Dernière activité 2 weeks ago

Bundled installer scripts. VS Code: install.{sh,ps1}. JetBrains IDEs (incl. Gateway / remote-dev-server): install-jetbrains.{sh,ps1}. Each script detects your OS (Windows / macOS / Ubuntu / WSL2) and is interactive.

Vernon Wee Hong KOH a révisé ce gist 2 weeks ago. Aller à la révision

1 file changed, 8 insertions, 2 deletions

install.ps1

@@ -92,10 +92,13 @@ foreach ($ext in $Extensions) {
92 92 Write-Host -NoNewline (" -> {0,-55} " -f $ext)
93 93 $stderrFile = [System.IO.Path]::GetTempFileName()
94 94 try {
95 - # Do not merge native stderr into PowerShell's error stream: warnings from
96 - # the VS Code CLI otherwise become terminating errors under Stop.
95 + # Windows PowerShell promotes native stderr to NativeCommandError under
96 + # Stop, even when redirected. Trust the CLI exit code instead.
97 + $previousErrorActionPreference = $ErrorActionPreference
98 + $ErrorActionPreference = 'Continue'
97 99 $output = & $codeCmd.Path @extraArgs --install-extension $ext --force 2> $stderrFile
98 100 $exitCode = $LASTEXITCODE
101 + $ErrorActionPreference = $previousErrorActionPreference
99 102 $stderr = Get-Content -LiteralPath $stderrFile
100 103
101 104 if ($exitCode -eq 0) {
@@ -114,6 +117,9 @@ foreach ($ext in $Extensions) {
114 117 $failedList += $ext
115 118 Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed
116 119 } finally {
120 + if ($null -ne $previousErrorActionPreference) {
121 + $ErrorActionPreference = $previousErrorActionPreference
122 + }
117 123 Remove-Item -LiteralPath $stderrFile -Force -ErrorAction SilentlyContinue
118 124 }
119 125 }

Vernon Wee Hong KOH a révisé ce gist 2 weeks ago. Aller à la révision

1 file changed, 11 insertions, 2 deletions

install.ps1

@@ -90,9 +90,15 @@ $failedList = @()
90 90
91 91 foreach ($ext in $Extensions) {
92 92 Write-Host -NoNewline (" -> {0,-55} " -f $ext)
93 + $stderrFile = [System.IO.Path]::GetTempFileName()
93 94 try {
94 - $output = & code @extraArgs --install-extension $ext --force 2>&1
95 - if ($LASTEXITCODE -eq 0) {
95 + # Do not merge native stderr into PowerShell's error stream: warnings from
96 + # the VS Code CLI otherwise become terminating errors under Stop.
97 + $output = & $codeCmd.Path @extraArgs --install-extension $ext --force 2> $stderrFile
98 + $exitCode = $LASTEXITCODE
99 + $stderr = Get-Content -LiteralPath $stderrFile
100 +
101 + if ($exitCode -eq 0) {
96 102 Write-Host "ok" -ForegroundColor Green
97 103 $installed++
98 104 } else {
@@ -100,12 +106,15 @@ foreach ($ext in $Extensions) {
100 106 $failed++
101 107 $failedList += $ext
102 108 $output | ForEach-Object { Write-Host " $_" -ForegroundColor DarkRed }
109 + $stderr | ForEach-Object { Write-Host " $_" -ForegroundColor DarkRed }
103 110 }
104 111 } catch {
105 112 Write-Host "FAILED" -ForegroundColor Red
106 113 $failed++
107 114 $failedList += $ext
108 115 Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed
116 + } finally {
117 + Remove-Item -LiteralPath $stderrFile -Force -ErrorAction SilentlyContinue
109 118 }
110 119 }
111 120

Vernon Wee Hong KOH a révisé ce gist 3 weeks ago. Aller à la révision

5 files changed, 366 insertions, 3 deletions

README.md

@@ -6,6 +6,8 @@ A bundle of cross-platform scripts for editor extensions and plugins, one comman
6 6 | ----------------------- | ----------------------------------- | ------------------------ |
7 7 | `install.sh` | VS Code | macOS, Linux (Ubuntu) |
8 8 | `install.ps1` | VS Code | Windows |
9 + | `settings.sh` | VS Code settings | macOS, Linux (Ubuntu) |
10 + | `settings.ps1` | VS Code settings | Windows |
9 11 | `install-jetbrains.sh` | JetBrains IDEs + remote-dev-server | macOS, Linux, WSL2 |
10 12 | `install-jetbrains.ps1` | JetBrains IDEs + Gateway Client | Windows |
11 13
@@ -18,7 +20,7 @@ Replace `<GIST>` with the raw base of this Gist (everything up to and including
18 20 ### VS Code
19 21
20 22 ```bash
21 - # 1. macOS / Ubuntu (Install to default profile from a LOCAL .vscode/extensions.json)
23 + # 1. macOS / Ubuntu (use local recommendations when present, otherwise install the bundled defaults)
22 24 bash <(curl -sSL https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.sh)
23 25
24 26 # 2. Install remote recommendation JSON to the default profile
@@ -39,6 +41,16 @@ iwr -useb https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a918
39 41 .\install.ps1 -ProfileName "WorkSetup"
40 42 ```
41 43
44 + When the bundled extensions are installed into the default profile, the installer also merges the curated settings described below. To apply only the settings:
45 +
46 + ```bash
47 + bash <(curl -sSL https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/settings.sh)
48 + ```
49 +
50 + ```powershell
51 + iwr -useb https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/settings.ps1 | iex
52 + ```
53 +
42 54 ### JetBrains
43 55
44 56 ```bash
@@ -89,8 +101,16 @@ Two IDs are easy to get wrong: `IdeaVIM` (xmlId is all-caps `VIM`) and `Key Prom
89 101 - `--profile "Name"` / `-ProfileName "Name"` installs into a named profile; VS Code creates the profile if it doesn't exist.
90 102 - `--force` is passed so re-runs are idempotent.
91 103 - Both scripts exit non-zero if any extension fails, and print a final summary.
104 + - `install.sh` applies the curated settings only when it falls back to its bundled extension list and no named profile is selected. A local or remote recommendation file does not trigger settings changes.
105 + - `install.ps1` applies the curated settings when its bundled extensions are installed into the default profile. Named profiles do not trigger settings changes.
106 +
107 + ### Curated VS Code settings
108 +
109 + `settings.sh` and `settings.ps1` update only the managed editor, wrapping, font-family, GitHub theme, and Material Icon Theme keys. Existing unrelated settings and values are preserved. The color theme is `GitHub Dark Default`, supplied by the bundled `github.github-vscode-theme` extension.
110 +
111 + Before changing an existing `settings.json`, the scripts create a timestamped `settings.json.backup.*` file. JSONC comments and trailing commas are accepted, but the merged file is normalized to formatted JSON; the backup retains the original comments and formatting. `editor.fontSize` is intentionally not managed.
92 112
93 - Prereq: the `code` CLI must be on `PATH`.
113 + Prereqs: the extension installers require the `code` CLI on `PATH`; the Bash installer and `settings.sh` also require `jq`. The standalone settings scripts do not require `code`.
94 114
95 115 - **macOS:** open VS Code → Cmd+Shift+P → "Shell Command: Install 'code' command in PATH".
96 116 - **Ubuntu:** install from <https://code.visualstudio.com/> or `sudo snap install --classic code`.
@@ -161,7 +181,7 @@ After Gateway installs finish, **close and re-open the Gateway Client** for it t
161 181
162 182 **`code` not on PATH.** See the VS Code installer details section above.
163 183
164 - **PowerShell refuses to run the script.** Each `.ps1` already sets `RemoteSigned` for the current process only. If your environment blocks that too, invoke explicitly:
184 + **PowerShell refuses to run an installer script.** The installer `.ps1` scripts attempt to set `RemoteSigned` for the current process only. If your environment blocks the file, invoke it explicitly:
165 185
166 186 ```powershell
167 187 powershell -ExecutionPolicy Bypass -File .\install.ps1

install.ps1

@@ -49,6 +49,21 @@ $Extensions = @(
49 49 'ms-vscode-remote.vscode-remote-extensionpack',
50 50 'pkief.material-icon-theme'
51 51 )
52 + $SettingsUrl = 'https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/settings.ps1'
53 +
54 + function Install-DefaultSettings {
55 + Write-Host ""
56 + Write-Host 'Applying curated settings to the default VS Code profile...' -ForegroundColor Cyan
57 +
58 + $localSettingsScript = if ($PSScriptRoot) { Join-Path $PSScriptRoot 'settings.ps1' } else { $null }
59 + if ($localSettingsScript -and (Test-Path -LiteralPath $localSettingsScript -PathType Leaf)) {
60 + & $localSettingsScript
61 + return
62 + }
63 +
64 + $settingsScript = (Invoke-WebRequest -UseBasicParsing -Uri $SettingsUrl).Content
65 + & ([scriptblock]::Create($settingsScript))
66 + }
52 67
53 68 $codeCmd = Get-Command code -ErrorAction SilentlyContinue
54 69 if (-not $codeCmd) {
@@ -104,3 +119,7 @@ if ($failed -gt 0) {
104 119 }
105 120 exit 1
106 121 }
122 +
123 + if ([string]::IsNullOrWhiteSpace($ProfileName)) {
124 + Install-DefaultSettings
125 + }

install.sh

@@ -21,6 +21,7 @@ EXTENSIONS=(
21 21 )
22 22 PROFILE=""
23 23 SOURCE=""
24 + SETTINGS_URL="https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/settings.sh"
24 25
25 26 usage() {
26 27 cat <<EOF
@@ -145,6 +146,25 @@ install_extension() {
145 146 fi
146 147 }
147 148
149 + apply_default_settings() {
150 + local script_dir settings_script
151 +
152 + script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
153 + settings_script="$script_dir/settings.sh"
154 +
155 + echo
156 + echo "Applying curated settings to the default VS Code profile..."
157 + if [[ -f "$settings_script" ]]; then
158 + bash "$settings_script"
159 + else
160 + if ! command -v curl >/dev/null 2>&1; then
161 + echo "Error: 'curl' is required to download the VS Code settings script." >&2
162 + return 1
163 + fi
164 + curl -fsSL "$SETTINGS_URL" | bash
165 + fi
166 + }
167 +
148 168 header="Installing ${#EXTENSIONS[@]} extension(s)"
149 169 [[ -n "$PROFILE" ]] && header+=" into profile '$PROFILE'"
150 170 echo "$header..."
@@ -179,3 +199,7 @@ if (( failed > 0 )); then
179 199 done
180 200 exit 1
181 201 fi
202 +
203 + if [[ "$USE_HARDCODED" == true && -z "$PROFILE" ]]; then
204 + apply_default_settings
205 + fi

settings.ps1(fichier créé)

@@ -0,0 +1,154 @@
1 + <# Merge curated defaults into the default VS Code profile settings. #>
2 +
3 + [CmdletBinding()]
4 + param()
5 +
6 + $ErrorActionPreference = 'Stop'
7 +
8 + function ConvertFrom-JsoncText {
9 + param([Parameter(Mandatory = $true)][string]$Text)
10 +
11 + $withoutComments = [System.Text.StringBuilder]::new()
12 + $state = 'Normal'
13 + $escaped = $false
14 +
15 + for ($i = 0; $i -lt $Text.Length; $i++) {
16 + $char = $Text[$i]
17 + $next = if ($i + 1 -lt $Text.Length) { $Text[$i + 1] } else { [char]0 }
18 +
19 + switch ($state) {
20 + 'Normal' {
21 + if ($char -eq '"') {
22 + [void]$withoutComments.Append($char)
23 + $state = 'String'
24 + } elseif ($char -eq '/' -and $next -eq '/') {
25 + $state = 'LineComment'
26 + $i++
27 + } elseif ($char -eq '/' -and $next -eq '*') {
28 + $state = 'BlockComment'
29 + $i++
30 + } else {
31 + [void]$withoutComments.Append($char)
32 + }
33 + }
34 + 'String' {
35 + [void]$withoutComments.Append($char)
36 + if ($escaped) {
37 + $escaped = $false
38 + } elseif ($char -eq '\') {
39 + $escaped = $true
40 + } elseif ($char -eq '"') {
41 + $state = 'Normal'
42 + }
43 + }
44 + 'LineComment' {
45 + if ($char -eq "`r" -or $char -eq "`n") {
46 + [void]$withoutComments.Append($char)
47 + $state = 'Normal'
48 + }
49 + }
50 + 'BlockComment' {
51 + if ($char -eq '*' -and $next -eq '/') {
52 + $state = 'Normal'
53 + $i++
54 + }
55 + }
56 + }
57 + }
58 +
59 + if ($state -eq 'BlockComment') {
60 + throw 'settings.json contains an unterminated block comment.'
61 + }
62 +
63 + $clean = $withoutComments.ToString()
64 + $withoutTrailingCommas = [System.Text.StringBuilder]::new()
65 + $state = 'Normal'
66 + $escaped = $false
67 +
68 + for ($i = 0; $i -lt $clean.Length; $i++) {
69 + $char = $clean[$i]
70 + if ($state -eq 'String') {
71 + [void]$withoutTrailingCommas.Append($char)
72 + if ($escaped) {
73 + $escaped = $false
74 + } elseif ($char -eq '\') {
75 + $escaped = $true
76 + } elseif ($char -eq '"') {
77 + $state = 'Normal'
78 + }
79 + continue
80 + }
81 +
82 + if ($char -eq '"') {
83 + [void]$withoutTrailingCommas.Append($char)
84 + $state = 'String'
85 + continue
86 + }
87 +
88 + if ($char -eq ',') {
89 + $lookAhead = $i + 1
90 + while ($lookAhead -lt $clean.Length -and [char]::IsWhiteSpace($clean[$lookAhead])) {
91 + $lookAhead++
92 + }
93 + if ($lookAhead -lt $clean.Length -and ($clean[$lookAhead] -eq '}' -or $clean[$lookAhead] -eq ']')) {
94 + continue
95 + }
96 + }
97 + [void]$withoutTrailingCommas.Append($char)
98 + }
99 +
100 + $json = $withoutTrailingCommas.ToString()
101 + if ([string]::IsNullOrWhiteSpace($json)) { $json = '{}' }
102 + return $json | ConvertFrom-Json
103 + }
104 +
105 + $settingsDir = if ($env:VSCODE_SETTINGS_DIR) {
106 + $env:VSCODE_SETTINGS_DIR
107 + } else {
108 + Join-Path $env:APPDATA 'Code\User'
109 + }
110 + $settingsFile = Join-Path $settingsDir 'settings.json'
111 + [void](New-Item -ItemType Directory -Path $settingsDir -Force)
112 +
113 + $backupFile = $null
114 + if (Test-Path -LiteralPath $settingsFile -PathType Leaf) {
115 + $timestamp = Get-Date -Format 'yyyyMMddHHmmssfff'
116 + $backupFile = "$settingsFile.backup.$timestamp"
117 + Copy-Item -LiteralPath $settingsFile -Destination $backupFile
118 + Write-Host "Backup created: $backupFile"
119 + $settings = ConvertFrom-JsoncText -Text ([System.IO.File]::ReadAllText($settingsFile))
120 + if ($settings -isnot [System.Management.Automation.PSCustomObject]) {
121 + throw 'settings.json must contain a JSON object.'
122 + }
123 + } else {
124 + $settings = [pscustomobject]@{}
125 + }
126 +
127 + $managedSettings = [ordered]@{
128 + 'editor.fontFamily' = "'Ubuntu Mono', 'IBM Plex Mono', 'JetBrains Mono', Hack, MonoLisa, monospace"
129 + 'editor.formatOnSave' = $true
130 + 'editor.lineHeight' = 24
131 + 'editor.renderWhitespace' = 'all'
132 + 'editor.rulers' = @(80, 100, 120)
133 + 'editor.wordWrap' = 'wordWrapColumn'
134 + 'editor.wordWrapColumn' = 80
135 + 'workbench.colorTheme' = 'GitHub Dark Default'
136 + 'workbench.iconTheme' = 'material-icon-theme'
137 + }
138 +
139 + foreach ($entry in $managedSettings.GetEnumerator()) {
140 + $settings | Add-Member -NotePropertyName $entry.Key -NotePropertyValue $entry.Value -Force
141 + }
142 +
143 + $tempFile = Join-Path $settingsDir ("settings.json.tmp.{0}" -f [guid]::NewGuid().ToString('N'))
144 + try {
145 + $json = $settings | ConvertTo-Json -Depth 100
146 + [System.IO.File]::WriteAllText($tempFile, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
147 + Move-Item -LiteralPath $tempFile -Destination $settingsFile -Force
148 + } catch {
149 + Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue
150 + if ($backupFile) { Write-Error "Could not merge settings. Original preserved at: $backupFile" }
151 + throw
152 + }
153 +
154 + Write-Host "VS Code settings updated: $settingsFile" -ForegroundColor Green

settings.sh(fichier créé)

@@ -0,0 +1,146 @@
1 + #!/usr/bin/env bash
2 +
3 + set -euo pipefail
4 + IFS=$'\n\t'
5 +
6 + if ! command -v jq >/dev/null 2>&1; then
7 + echo "Error: 'jq' is required to merge VS Code settings." >&2
8 + exit 1
9 + fi
10 +
11 + case "$(uname -s)" in
12 + Linux)
13 + SETTINGS_DIR="${VSCODE_SETTINGS_DIR:-$HOME/.config/Code/User}"
14 + ;;
15 + Darwin)
16 + SETTINGS_DIR="${VSCODE_SETTINGS_DIR:-$HOME/Library/Application Support/Code/User}"
17 + ;;
18 + *)
19 + echo "Error: unsupported OS '$(uname -s)'. Use settings.ps1 on Windows." >&2
20 + exit 1
21 + ;;
22 + esac
23 +
24 + mkdir -p "$SETTINGS_DIR"
25 + SETTINGS_FILE="$SETTINGS_DIR/settings.json"
26 +
27 + # Update the target of a dotfiles-managed symlink instead of replacing the link.
28 + while [[ -L "$SETTINGS_FILE" ]]; do
29 + LINK_TARGET="$(readlink "$SETTINGS_FILE")"
30 + if [[ "$LINK_TARGET" == /* ]]; then
31 + SETTINGS_FILE="$LINK_TARGET"
32 + else
33 + SETTINGS_FILE="$(dirname "$SETTINGS_FILE")/$LINK_TARGET"
34 + fi
35 + done
36 + SETTINGS_DIR="$(dirname "$SETTINGS_FILE")"
37 + mkdir -p "$SETTINGS_DIR"
38 +
39 + if [[ -f "$SETTINGS_FILE" ]]; then
40 + BACKUP_FILE="$SETTINGS_FILE.backup.$(date +%Y%m%d%H%M%S).$$"
41 + cp "$SETTINGS_FILE" "$BACKUP_FILE"
42 + echo "Backup created: $BACKUP_FILE"
43 + else
44 + BACKUP_FILE=""
45 + fi
46 +
47 + INPUT_FILE="$SETTINGS_FILE"
48 + if [[ ! -f "$INPUT_FILE" ]]; then
49 + INPUT_FILE="/dev/null"
50 + fi
51 +
52 + TEMP_FILE="$(mktemp "$SETTINGS_DIR/settings.json.tmp.XXXXXX")"
53 + trap 'rm -f "$TEMP_FILE"' EXIT
54 +
55 + if ! jq -Rs '
56 + def strip_comments:
57 + reduce (explode[]) as $c (
58 + {out: [], state: "normal", escaped: false};
59 + if .state == "normal" then
60 + if $c == 34 then .out += [$c] | .state = "string"
61 + elif $c == 47 then .state = "slash"
62 + else .out += [$c]
63 + end
64 + elif .state == "slash" then
65 + if $c == 47 then .state = "line_comment"
66 + elif $c == 42 then .state = "block_comment"
67 + else
68 + .out += [47, $c]
69 + | .state = (if $c == 34 then "string" else "normal" end)
70 + end
71 + elif .state == "line_comment" then
72 + if $c == 10 or $c == 13 then .out += [$c] | .state = "normal" else . end
73 + elif .state == "block_comment" then
74 + if $c == 42 then .state = "block_star" else . end
75 + elif .state == "block_star" then
76 + if $c == 47 then .state = "normal"
77 + elif $c != 42 then .state = "block_comment"
78 + else .
79 + end
80 + else
81 + .out += [$c]
82 + | if .escaped then .escaped = false
83 + elif $c == 92 then .escaped = true
84 + elif $c == 34 then .state = "normal"
85 + else .
86 + end
87 + end
88 + )
89 + | if .state == "block_comment" or .state == "block_star" then
90 + error("unterminated block comment")
91 + else
92 + (if .state == "slash" then .out + [47] else .out end)
93 + end
94 + | implode;
95 +
96 + def strip_trailing_commas:
97 + reduce (explode[]) as $c (
98 + {out: [], state: "normal", escaped: false, comma: false, ws: []};
99 + if .state == "string" then
100 + .out += [$c]
101 + | if .escaped then .escaped = false
102 + elif $c == 92 then .escaped = true
103 + elif $c == 34 then .state = "normal"
104 + else .
105 + end
106 + elif .comma and ($c == 9 or $c == 10 or $c == 13 or $c == 32) then
107 + .ws += [$c]
108 + elif .comma and ($c == 93 or $c == 125) then
109 + .out += [$c] | .comma = false | .ws = []
110 + else
111 + (if .comma then .out += [44] + .ws | .comma = false | .ws = [] else . end)
112 + | if $c == 44 then .comma = true
113 + else
114 + .out += [$c]
115 + | if $c == 34 then .state = "string" else . end
116 + end
117 + end
118 + )
119 + | (if .comma then .out + [44] + .ws else .out end)
120 + | implode;
121 +
122 + (if test("^[[:space:]]*$") then "{}" else . end)
123 + | strip_comments
124 + | strip_trailing_commas
125 + | fromjson
126 + | if type != "object" then error("settings.json must contain a JSON object") else . end
127 + | . + {
128 + "editor.fontFamily": "\u0027Ubuntu Mono\u0027, \u0027IBM Plex Mono\u0027, \u0027JetBrains Mono\u0027, Hack, MonoLisa, monospace",
129 + "editor.formatOnSave": true,
130 + "editor.lineHeight": 24,
131 + "editor.renderWhitespace": "all",
132 + "editor.rulers": [80, 100, 120],
133 + "editor.wordWrap": "wordWrapColumn",
134 + "editor.wordWrapColumn": 80,
135 + "workbench.colorTheme": "GitHub Dark Default",
136 + "workbench.iconTheme": "material-icon-theme"
137 + }
138 + ' "$INPUT_FILE" >"$TEMP_FILE"; then
139 + echo "Error: could not parse and merge $SETTINGS_FILE." >&2
140 + [[ -n "$BACKUP_FILE" ]] && echo "Original preserved at: $BACKUP_FILE" >&2
141 + exit 1
142 + fi
143 +
144 + mv "$TEMP_FILE" "$SETTINGS_FILE"
145 + trap - EXIT
146 + echo "VS Code settings updated: $SETTINGS_FILE"

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

2 files changed, 7 insertions, 2 deletions

install-jetbrains.ps1

@@ -380,7 +380,10 @@ $installedPluginInstances = @()
380 380 if ($action -eq 'Install') {
381 381 $pluginChoices = $PluginCatalog
382 382 } else {
383 - $installedPluginInstances = @($selectedIdes | ForEach-Object { Get-InstalledPlugins -Ide $_ })
383 + $installedPluginInstances = @($selectedIdes |
384 + ForEach-Object { Get-InstalledPlugins -Ide $_ } |
385 + Group-Object Path |
386 + ForEach-Object { $_.Group[0] })
384 387 $pluginChoices = @($installedPluginInstances |
385 388 Group-Object Id |
386 389 ForEach-Object { [pscustomobject]@{ Id = $_.Name; Label = $_.Group[0].Label } } |

install-jetbrains.sh

@@ -530,7 +530,7 @@ else
530 530 exit 1
531 531 fi
532 532
533 - declare -A seen_plugin_ids=()
533 + declare -A seen_plugin_ids=() seen_plugin_paths=()
534 534 for ide_label in "${SELECTED_IDES[@]}"; do
535 535 itype="${label_type[$ide_label]}"
536 536 launcher="${label_launcher[$ide_label]}"
@@ -543,6 +543,8 @@ else
543 543 while IFS= read -r -d '' item; do
544 544 metadata="$(plugin_metadata "$item" || true)"
545 545 [[ -n "$metadata" ]] || continue
546 + [[ -z "${seen_plugin_paths[$item]+x}" ]] || continue
547 + seen_plugin_paths["$item"]=1
546 548 IFS='|' read -r pid plabel <<<"$metadata"
547 549 INSTALLED_PLUGIN_RECORDS+=("${pid}|${plabel}|${item}|${plugin_root}|${ide_label}")
548 550 if [[ -z "${seen_plugin_ids[$pid]+x}" ]]; then

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

3 files changed, 500 insertions, 34 deletions

README.md

@@ -1,6 +1,6 @@
1 - # VS Code + JetBrains Extension Installers
1 + # VS Code + JetBrains Extension Managers
2 2
3 - A bundle of cross-platform scripts that install a curated set of editor extensions / plugins, one command per platform.
3 + A bundle of cross-platform scripts for editor extensions and plugins, one command per platform. The JetBrains scripts install and uninstall plugins; the VS Code scripts install extensions.
4 4
5 5 | File | Editor | Platforms |
6 6 | ----------------------- | ----------------------------------- | ------------------------ |
@@ -42,12 +42,12 @@ iwr -useb https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a918
42 42 ### JetBrains
43 43
44 44 ```bash
45 - # macOS / Ubuntu / WSL2 — interactive picker for IDEs and plugins
45 + # macOS / Ubuntu / WSL2 — IDE, action, then plugin picker
46 46 bash <(curl -sSL https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install-jetbrains.sh)
47 47 ```
48 48
49 49 ```powershell
50 - # Windows — interactive picker
50 + # Windows — IDE, action, then plugin picker
51 51 iwr -useb https://opengist.resetrix.work/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install-jetbrains.ps1 | iex
52 52 ```
53 53
@@ -104,8 +104,12 @@ Both JetBrains scripts:
104 104
105 105 1. Detect the host OS.
106 106 2. Scan the system for installed JetBrains IDEs and (where applicable) Gateway / remote-dev targets.
107 - 3. Show an interactive multi-select picker for IDEs and another for plugins.
108 - 4. Run each IDE's `installPlugins` against every selected plugin.
107 + 3. Show the IDE picker first.
108 + 4. Ask whether to install or uninstall.
109 + 5. For install, show the curated catalog; for uninstall, show the user-installed plugins actually found in the selected IDEs.
110 + 6. Install the selected marketplace IDs or, after showing exact paths and asking for confirmation, remove the selected plugin files.
111 +
112 + JetBrains AI Assistant is identified by its marketplace ID, `com.intellij.ml.llm`, so uninstall works even though its on-disk directory is normally named `ml-llm`.
109 113
110 114 ### What gets scanned
111 115
@@ -134,9 +138,15 @@ WSL2 detection works by grepping `microsoft` in `/proc/version`. The Windows use
134 138
135 139 ### Picker
136 140
137 - - **Bash:** uses `fzf --multi` if `fzf` is on `PATH` (TAB to toggle, Enter to confirm); otherwise prints a numbered list and accepts comma-separated indexes (`1,3,5`) or `a` for all.
141 + - **Bash:** uses `fzf --multi` if `fzf` is on `PATH` (TAB to toggle, Enter to confirm); otherwise prints a numbered list and accepts space-separated indexes (`1 3 5`), exclusions (`!2 !4`), or `a` for all.
138 142 - **PowerShell:** uses `Out-GridView -OutputMode Multiple` when available; otherwise the same numbered/comma-input fallback.
139 143
144 + ### Uninstall behavior
145 +
146 + JetBrains launchers provide `installPlugins` but no matching uninstall command. The scripts therefore resolve the selected IDE's `dataDirectoryName` from `product-info.json`, inspect each user-installed plugin's `META-INF/plugin.xml`, and present those plugin names and IDs for selection. Before removal, the script prints every resolved IDE and path and requires confirmation.
147 +
148 + Only user-installed plugins are offered. Bundled plugins cannot be removed this way and must be disabled in the IDE. Restart the selected IDEs after uninstalling.
149 +
140 150 ### Gateway Client path (Windows)
141 151
142 152 When the script detects `%LOCALAPPDATA%\JetBrains\JetBrainsClient*` and you select it from the picker, the install logic switches: the bundled Gateway client has no `installPlugins` CLI, so the script reads the client's `build.txt` and downloads each plugin directly from `plugins.jetbrains.com`, unpacking it into the client's `plugins\` folder.
@@ -159,7 +169,7 @@ powershell -ExecutionPolicy Bypass -File .\install.ps1
159 169
160 170 **`installPlugins` reports a plugin is already installed.** Harmless — it's a no-op. Both scripts treat it as success.
161 171
162 - **"Only one instance of IDEA can be run at a time."** The IDE is currently open and holds the config-dir lock. Both JetBrains installers run a preflight check before installing: if a target IDE looks running, they prompt you to close it and press Enter to retry. If a process slips past the preflight, the install loop short-circuits with the same hint instead of repeating the error per plugin.
172 + **"Only one instance of IDEA can be run at a time."** The IDE is currently open and holds the config-dir lock. Both JetBrains scripts run a preflight check before changing plugins: if a target IDE looks running, they prompt you to close it and press Enter to retry. If a process slips past the preflight, the install loop short-circuits with the same hint instead of repeating the error per plugin.
163 173
164 174 **No JetBrains IDEs detected.** Install one via the [JetBrains Toolbox](https://www.jetbrains.com/toolbox-app/) (which sets up the standard paths the script scans), or install standalone — both layouts are supported.
165 175
@@ -173,4 +183,4 @@ powershell -ExecutionPolicy Bypass -File .\install.ps1
173 183
174 184 - Scripts are idempotent — already-installed extensions/plugins are skipped or upgraded in place.
175 185 - Exit code is non-zero if any extension/plugin failed.
176 - - The JetBrains scripts ask every time; there's no flag to skip the picker by design.
186 + - The JetBrains scripts ask for IDE, action, and plugin choices every time; there's no flag to skip the picker by design.

install-jetbrains.ps1

@@ -1,7 +1,7 @@
1 1 <#
2 2 .SYNOPSIS
3 - Install JetBrains plugins into locally installed IDEs (and into the
4 - JetBrains Gateway Client) on Windows.
3 + Install or uninstall JetBrains plugins in locally installed IDEs (and in
4 + the JetBrains Gateway Client) on Windows.
5 5
6 6 .DESCRIPTION
7 7 Detects every JetBrains IDE under %LOCALAPPDATA%\Programs\* (standalone
@@ -9,8 +9,10 @@
9 9 %LOCALAPPDATA%\JetBrains\Toolbox\scripts\*.cmd, and the most-recent
10 10 JetBrains Gateway Client under %LOCALAPPDATA%\JetBrains\JetBrainsClient*.
11 11
12 - You multi-select IDE(s) and plugin(s) interactively. Standard IDEs get
13 - "installPlugins <id>..." invoked once with all selected plugins. The
12 + You select IDE(s), choose install or uninstall, and then select plugin(s).
13 + Standard IDEs get "installPlugins <id>..." invoked once with all selected
14 + plugins. Uninstall reads plugin IDs from the selected IDEs' user plugin
15 + directories and removes only the entries selected by the user. The
14 16 Gateway Client path is special: the bundled client has no installPlugins
15 17 CLI, so plugins are downloaded directly from plugins.jetbrains.com and
16 18 unpacked into the client's plugins folder (mirrors the user's
@@ -37,6 +39,11 @@ try {
37 39
38 40 $ErrorActionPreference = 'Stop'
39 41 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
42 + try {
43 + Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
44 + } catch {
45 + Write-Verbose ("Could not preload ZIP support: {0}" -f $_.Exception.Message)
46 + }
40 47
41 48 # ------------------------------------------------------------------
42 49 # Canonical plugin catalog. Each entry: @{ Id; Label }
@@ -197,6 +204,168 @@ function Select-Multi {
197 204 }
198 205 }
199 206
207 + function Select-Action {
208 + while ($true) {
209 + Write-Host ""
210 + Write-Host "What do you want to do?" -ForegroundColor Cyan
211 + Write-Host " 1) Install plugins"
212 + Write-Host " 2) Uninstall plugins"
213 + $choice = Read-Host "Action (1 or 2)"
214 + switch ($choice.Trim().ToLower()) {
215 + { $_ -in @('1', 'i', 'install') } { return 'Install' }
216 + { $_ -in @('2', 'u', 'uninstall') } { return 'Uninstall' }
217 + default { Write-Host " invalid input; enter 1 for install or 2 for uninstall." -ForegroundColor Yellow }
218 + }
219 + }
220 + }
221 +
222 + # Find product-info.json for the selected launcher and return the IDE's
223 + # dataDirectoryName. This prevents uninstall from touching unrelated profiles.
224 + function Get-ProductDataName {
225 + param([Parameter(Mandatory)] [psobject]$Ide)
226 +
227 + if ($Ide.Type -eq 'Gateway') { return $null }
228 + $probe = $Ide.Launcher
229 +
230 + # Toolbox .cmd shims normally contain the real launcher path.
231 + if ($Ide.Type -eq 'WinCmd' -and (Test-Path -LiteralPath $probe)) {
232 + try {
233 + $cmdText = Get-Content -LiteralPath $probe -Raw -ErrorAction Stop
234 + $match = [regex]::Match($cmdText, '"([^"\r\n]+\.exe)"', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
235 + if ($match.Success) {
236 + $expanded = [Environment]::ExpandEnvironmentVariables($match.Groups[1].Value)
237 + if (Test-Path -LiteralPath $expanded) { $probe = $expanded }
238 + }
239 + } catch {
240 + Write-Verbose ("Could not resolve Toolbox shim {0}: {1}" -f $probe, $_.Exception.Message)
241 + }
242 + }
243 +
244 + try {
245 + $item = Get-Item -LiteralPath $probe -ErrorAction Stop
246 + $dir = if ($item.PSIsContainer) { $item.FullName } else { $item.Directory.FullName }
247 + } catch { return $null }
248 +
249 + while ($dir) {
250 + foreach ($relative in @('product-info.json', 'Resources\product-info.json')) {
251 + $infoPath = Join-Path $dir $relative
252 + if (-not (Test-Path -LiteralPath $infoPath)) { continue }
253 + try {
254 + $info = Get-Content -LiteralPath $infoPath -Raw -ErrorAction Stop | ConvertFrom-Json
255 + if ($info.dataDirectoryName) { return [string]$info.dataDirectoryName }
256 + } catch {
257 + Write-Verbose ("Could not read {0}: {1}" -f $infoPath, $_.Exception.Message)
258 + }
259 + }
260 + $parent = [System.IO.Directory]::GetParent($dir)
261 + if (-not $parent) { break }
262 + $dir = $parent.FullName
263 + }
264 + return $null
265 + }
266 +
267 + function Get-PluginRoot {
268 + param([Parameter(Mandatory)] [psobject]$Ide)
269 + if ($Ide.Type -eq 'Gateway') { return (Join-Path $Ide.Client 'plugins') }
270 +
271 + $dataName = Get-ProductDataName -Ide $Ide
272 + if (-not $dataName) { return $null }
273 + return (Join-Path (Join-Path $env:APPDATA 'JetBrains') (Join-Path $dataName 'plugins'))
274 + }
275 +
276 + function ConvertFrom-PluginXml {
277 + param([Parameter(Mandatory)] [string]$Text)
278 + try {
279 + $settings = New-Object System.Xml.XmlReaderSettings
280 + $settings.DtdProcessing = [System.Xml.DtdProcessing]::Prohibit
281 + $stringReader = New-Object System.IO.StringReader($Text)
282 + $reader = [System.Xml.XmlReader]::Create($stringReader, $settings)
283 + $doc = New-Object System.Xml.XmlDocument
284 + $doc.XmlResolver = $null
285 + $doc.Load($reader)
286 + $reader.Dispose()
287 + $stringReader.Dispose()
288 +
289 + $idNode = $doc.SelectSingleNode('/idea-plugin/id')
290 + $nameNode = $doc.SelectSingleNode('/idea-plugin/name')
291 + $id = if ($idNode) { $idNode.InnerText.Trim() } elseif ($nameNode) { $nameNode.InnerText.Trim() } else { $null }
292 + $name = if ($nameNode) { $nameNode.InnerText.Trim() } else { $id }
293 + if ($id) { return [pscustomobject]@{ Id = $id; Label = $name } }
294 + } catch {
295 + Write-Verbose ("Could not parse plugin.xml: {0}" -f $_.Exception.Message)
296 + }
297 + return $null
298 + }
299 +
300 + function Get-ArchivePluginDescriptor {
301 + param([Parameter(Mandatory)] [string]$Path)
302 + $zip = $null
303 + try {
304 + $zip = [System.IO.Compression.ZipFile]::OpenRead($Path)
305 + $entry = $zip.Entries | Where-Object { $_.FullName -eq 'META-INF/plugin.xml' } | Select-Object -First 1
306 + if (-not $entry) { return $null }
307 + $stream = $entry.Open()
308 + $reader = New-Object System.IO.StreamReader($stream)
309 + $text = $reader.ReadToEnd()
310 + $reader.Dispose()
311 + $stream.Dispose()
312 + return (ConvertFrom-PluginXml -Text $text)
313 + } catch {
314 + Write-Verbose ("Could not inspect plugin archive {0}: {1}" -f $Path, $_.Exception.Message)
315 + return $null
316 + } finally {
317 + if ($zip) { $zip.Dispose() }
318 + }
319 + }
320 +
321 + function Get-PluginDescriptor {
322 + param([Parameter(Mandatory)] [System.IO.FileSystemInfo]$Item)
323 +
324 + if (-not $Item.PSIsContainer) {
325 + return (Get-ArchivePluginDescriptor -Path $Item.FullName)
326 + }
327 +
328 + $looseXml = Join-Path $Item.FullName 'META-INF\plugin.xml'
329 + if (Test-Path -LiteralPath $looseXml) {
330 + try {
331 + return (ConvertFrom-PluginXml -Text (Get-Content -LiteralPath $looseXml -Raw -ErrorAction Stop))
332 + } catch {
333 + Write-Verbose ("Could not inspect {0}: {1}" -f $looseXml, $_.Exception.Message)
334 + }
335 + }
336 +
337 + $archives = Get-ChildItem -LiteralPath $Item.FullName -Recurse -File -ErrorAction SilentlyContinue |
338 + Where-Object { $_.Extension -in @('.jar', '.zip') }
339 + foreach ($archive in $archives) {
340 + $descriptor = Get-ArchivePluginDescriptor -Path $archive.FullName
341 + if ($descriptor) { return $descriptor }
342 + }
343 + return $null
344 + }
345 +
346 + function Get-InstalledPlugins {
347 + param([Parameter(Mandatory)] [psobject]$Ide)
348 + $root = Get-PluginRoot -Ide $Ide
349 + if (-not $root) {
350 + Write-Host ("Could not resolve the plugin directory for: {0}" -f $Ide.Label) -ForegroundColor Yellow
351 + return
352 + }
353 + if (-not (Test-Path -LiteralPath $root)) { return }
354 +
355 + Get-ChildItem -LiteralPath $root -Force -ErrorAction SilentlyContinue | ForEach-Object {
356 + $descriptor = Get-PluginDescriptor -Item $_
357 + if ($descriptor) {
358 + [pscustomobject]@{
359 + Id = $descriptor.Id
360 + Label = $descriptor.Label
361 + Path = $_.FullName
362 + PluginRoot = $root
363 + TargetLabel = $Ide.Label
364 + }
365 + }
366 + }
367 + }
368 +
200 369 Write-Host ("Detected {0} JetBrains target(s)." -f $Candidates.Count) -ForegroundColor Green
201 370
202 371 $selectedIdes = Select-Multi -Title "Pick IDE(s)" -Items $Candidates -Property 'Label'
@@ -205,7 +374,26 @@ if ($selectedIdes.Count -eq 0) {
205 374 exit 1
206 375 }
207 376
208 - $selectedPlugins = Select-Multi -Title "Pick plugin(s)" -Items $PluginCatalog -Property 'Label'
377 + $action = Select-Action
378 + $installedPluginInstances = @()
379 +
380 + if ($action -eq 'Install') {
381 + $pluginChoices = $PluginCatalog
382 + } else {
383 + $installedPluginInstances = @($selectedIdes | ForEach-Object { Get-InstalledPlugins -Ide $_ })
384 + $pluginChoices = @($installedPluginInstances |
385 + Group-Object Id |
386 + ForEach-Object { [pscustomobject]@{ Id = $_.Name; Label = $_.Group[0].Label } } |
387 + Sort-Object Label)
388 +
389 + if ($pluginChoices.Count -eq 0) {
390 + Write-Host "No removable user-installed plugins were found for the selected IDE(s)." -ForegroundColor Yellow
391 + Write-Host "Bundled plugins cannot be uninstalled; disable them in the IDE instead." -ForegroundColor Yellow
392 + exit 0
393 + }
394 + }
395 +
396 + $selectedPlugins = Select-Multi -Title ("Pick plugin(s) to {0}" -f $action.ToLower()) -Items $pluginChoices -Property 'Label'
209 397 if ($selectedPlugins.Count -eq 0) {
210 398 Write-Host "No plugins selected; exiting." -ForegroundColor Red
211 399 exit 1
@@ -213,13 +401,20 @@ if ($selectedPlugins.Count -eq 0) {
213 401
214 402 # ------------------------------------------------------------------
215 403 # Running-IDE preflight
404 + # Installing or deleting plugin files requires the selected IDE to be closed.
216 405 # installPlugins refuses to run while the IDE GUI holds the config-dir lock
217 406 # ("Only one instance of IDEA can be run at a time."). Best-effort detection
218 407 # via Get-Process. Gateway Client type doesn't need this (no CLI invoked).
219 408 # ------------------------------------------------------------------
220 409 function Test-IdeRunning {
221 410 param([Parameter(Mandatory)] [psobject]$Ide)
222 - if ($Ide.Type -eq 'Gateway') { return $false }
411 + if ($Ide.Type -eq 'Gateway') {
412 + if ($action -eq 'Install') { return $false }
413 + foreach ($name in @('jetbrains_client64', 'jetbrains_client', 'JetBrainsClient')) {
414 + if (Get-Process -Name $name -ErrorAction SilentlyContinue) { return $true }
415 + }
416 + return $false
417 + }
223 418
224 419 # For standalone IDE launchers the process name is the exe basename.
225 420 # For Toolbox .cmd shims, the spawned process is e.g. idea64 -> use prefix.
@@ -238,15 +433,65 @@ while ($true) {
238 433 if ($running.Count -eq 0) { break }
239 434
240 435 Write-Host ""
241 - Write-Host "The following IDE(s) appear to be running — installPlugins needs them closed:" -ForegroundColor Yellow
436 + Write-Host "The following IDE(s) appear to be running — plugin changes need them closed:" -ForegroundColor Yellow
242 437 foreach ($r in $running) { Write-Host (" - {0}" -f $r.Label) -ForegroundColor Yellow }
243 438 Write-Host ""
244 439 Read-Host "Close them, then press Enter to retry (Ctrl+C to abort)" | Out-Null
245 440 }
246 441
247 442 # ------------------------------------------------------------------
248 - # Install loop
443 + # Uninstall or install
249 444 # ------------------------------------------------------------------
445 + if ($action -eq 'Uninstall') {
446 + $selectedIds = @($selectedPlugins | ForEach-Object { $_.Id })
447 + $toRemove = @($installedPluginInstances | Where-Object { $_.Id -in $selectedIds } | Where-Object {
448 + $parent = [System.IO.Directory]::GetParent($_.Path)
449 + $parent -and ($parent.FullName.TrimEnd('\') -eq $_.PluginRoot.TrimEnd('\'))
450 + })
451 +
452 + if ($toRemove.Count -eq 0) {
453 + Write-Host "None of the selected plugins are installed in the selected IDE(s)." -ForegroundColor Yellow
454 + exit 0
455 + }
456 +
457 + Write-Host ""
458 + Write-Host "The following plugin installations will be removed:" -ForegroundColor Yellow
459 + foreach ($plugin in $toRemove) {
460 + Write-Host (" - {0} ({1})" -f $plugin.Label, $plugin.Id)
461 + Write-Host (" IDE: {0}" -f $plugin.TargetLabel)
462 + Write-Host (" Path: {0}" -f $plugin.Path)
463 + }
464 + $confirm = Read-Host "Continue? [y/N]"
465 + if ($confirm -notmatch '^[Yy]$') {
466 + Write-Host "Uninstall cancelled."
467 + exit 0
468 + }
469 +
470 + $removed = 0
471 + $failed = 0
472 + $failedList = @()
473 + foreach ($plugin in $toRemove) {
474 + Write-Host -NoNewline ("Removing {0} from {1}... " -f $plugin.Label, $plugin.TargetLabel)
475 + try {
476 + Remove-Item -LiteralPath $plugin.Path -Recurse -Force -ErrorAction Stop
477 + Write-Host "ok" -ForegroundColor Green
478 + $removed++
479 + } catch {
480 + Write-Host "FAILED" -ForegroundColor Red
481 + Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed
482 + $failed++
483 + $failedList += ("{0} :: {1}" -f $plugin.TargetLabel, $plugin.Id)
484 + }
485 + }
486 +
487 + Write-Host ("Done. Removed: {0}, Failed: {1}. Restart the IDE(s) to finish." -f $removed, $failed)
488 + if ($failed -gt 0) {
489 + foreach ($entry in $failedList) { Write-Host (" - {0}" -f $entry) -ForegroundColor Red }
490 + exit 1
491 + }
492 + exit 0
493 + }
494 +
250 495 $installed = 0
251 496 $failed = 0
252 497 $failedList = @()

install-jetbrains.sh

@@ -1,11 +1,11 @@
1 1 #!/usr/bin/env bash
2 2 #
3 - # JetBrains IDE plugin installer for macOS, Linux (Ubuntu Desktop) and WSL2.
3 + # JetBrains IDE plugin manager for macOS, Linux (Ubuntu Desktop) and WSL2.
4 4 #
5 5 # Detects every locally installed JetBrains IDE (Toolbox + standalone) and,
6 6 # on Linux, any JetBrains Gateway remote-dev-server.sh distributions. Lets
7 - # you multi-select IDE(s) and plugin(s) interactively, then installs each
8 - # plugin into each selected IDE.
7 + # you multi-select IDE(s), choose install or uninstall, and then select the
8 + # plugins to manage.
9 9 #
10 10 # Picker UX:
11 11 # - Uses fzf with --multi when fzf is on PATH (TAB to toggle, Enter to confirm).
@@ -54,14 +54,15 @@ usage() {
54 54 cat <<EOF
55 55 Usage: $(basename "$0")
56 56
57 - Interactive JetBrains IDE plugin installer for macOS, Linux and WSL2.
57 + Interactive JetBrains IDE plugin manager for macOS, Linux and WSL2.
58 58
59 59 The script:
60 60 1. Detects your OS (macOS / Linux / WSL2)
61 61 2. Scans for installed JetBrains IDEs (Toolbox + standalone) and any
62 62 local remote-dev-server.sh distributions
63 - 3. Lets you multi-select IDE(s) and plugin(s)
64 - 4. Runs each launcher's "installPlugins" command for every selected plugin
63 + 3. Lets you choose whether to install or uninstall plugins
64 + 4. Installs catalog plugins, or lists the selected IDEs' installed plugins
65 + and lets you choose which ones to remove
65 66
66 67 Use \`brew install fzf\` (or your distro's package manager) for a nicer picker.
67 68 EOF
@@ -366,6 +367,119 @@ pick_multi() {
366 367 done
367 368 }
368 369
370 + pick_action() {
371 + local input
372 + while :; do
373 + {
374 + echo
375 + echo "What do you want to do?"
376 + echo " 1) Install plugins"
377 + echo " 2) Uninstall plugins"
378 + printf 'Action (1 or 2): '
379 + } >&2
380 + if ! IFS= read -r input </dev/tty; then
381 + echo "no tty available; run interactively" >&2
382 + return 1
383 + fi
384 + case "$input" in
385 + 1|i|I|install|Install) printf 'install\n'; return ;;
386 + 2|u|U|uninstall|Uninstall) printf 'uninstall\n'; return ;;
387 + *) echo " invalid input; enter 1 for install or 2 for uninstall." >&2 ;;
388 + esac
389 + done
390 + }
391 +
392 + # Locate product-info.json for a launcher and print dataDirectoryName. This
393 + # ties uninstalling to the IDE selected above instead of scanning every
394 + # JetBrains profile on the machine.
395 + product_data_name() {
396 + local launcher="$1" probe="$1" dir info parent raw
397 +
398 + # Resolve a symlinked Toolbox launcher when possible.
399 + if [[ -L "$probe" ]]; then
400 + raw="$(readlink "$probe" 2>/dev/null || true)"
401 + if [[ -n "$raw" ]]; then
402 + [[ "$raw" == /* ]] || raw="$(dirname "$probe")/$raw"
403 + probe="$raw"
404 + fi
405 + fi
406 +
407 + # Toolbox .cmd shims point at the real Windows launcher. Resolve that path
408 + # when running under WSL so product-info.json can be found beside the IDE.
409 + if [[ "$launcher" == *.cmd && -f "$launcher" ]]; then
410 + raw="$(tr -d '\r' <"$launcher" | sed -nE 's/.*"([A-Za-z]:\\[^"]+\.exe)".*/\1/p' | head -n 1)"
411 + if [[ -n "$raw" ]] && command -v wslpath >/dev/null 2>&1; then
412 + probe="$(wslpath -u "$raw" 2>/dev/null || printf '%s' "$launcher")"
413 + fi
414 + elif [[ "$OS_KIND" == "macos" && -f "$launcher" ]]; then
415 + # Toolbox shell launchers usually contain the real *.app executable path.
416 + raw="$(sed -nE 's/.*"([^\"]+\.app\/Contents\/MacOS\/[^\"]+)".*/\1/p' "$launcher" | head -n 1)"
417 + raw="${raw/#\~\//$HOME/}"
418 + raw="${raw/#\$HOME\//$HOME/}"
419 + [[ -n "$raw" && -e "$raw" ]] && probe="$raw"
420 + fi
421 +
422 + dir="$probe"
423 + [[ -f "$dir" ]] && dir="$(dirname "$dir")"
424 + while [[ -n "$dir" && "$dir" != "/" ]]; do
425 + for info in "$dir/product-info.json" "$dir/Resources/product-info.json"; do
426 + [[ -f "$info" ]] || continue
427 + sed -nE 's/.*"dataDirectoryName"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$info" | head -n 1
428 + return
429 + done
430 + parent="$(dirname "$dir")"
431 + [[ "$parent" == "$dir" ]] && break
432 + dir="$parent"
433 + done
434 + }
435 +
436 + plugin_root_for_target() {
437 + local launcher="$1" type="$2" data_name
438 + data_name="$(product_data_name "$launcher")"
439 + [[ -n "$data_name" ]] || return 1
440 +
441 + if [[ "$type" == "remotedev" ]]; then
442 + printf '%s/.local/shared/JetBrains/%s\n' "$HOME" "$data_name"
443 + elif [[ "$OS_KIND" == "macos" ]]; then
444 + printf '%s/Library/Application Support/JetBrains/%s/plugins\n' "$HOME" "$data_name"
445 + elif [[ "$OS_KIND" == "wsl2" && ( "$type" == "wincmd" || "$launcher" == /mnt/* ) ]]; then
446 + printf '%s/AppData/Roaming/JetBrains/%s/plugins\n' "$(win_user_dir)" "$data_name"
447 + else
448 + printf '%s/.local/share/JetBrains/%s\n' "$HOME" "$data_name"
449 + fi
450 + }
451 +
452 + xml_tag_value() {
453 + local xml="$1" tag="$2" rest
454 + [[ "$xml" == *"<$tag>"* ]] || return 1
455 + rest="${xml#*<$tag>}"
456 + rest="${rest%%</$tag>*}"
457 + # plugin.xml values are normally plain text; trim surrounding whitespace.
458 + printf '%s' "$rest" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
459 + }
460 +
461 + # Print "id|name" for one top-level item in an IDE's plugins directory.
462 + plugin_metadata() {
463 + local item="$1" xml="" archive id name
464 + if [[ -f "$item/META-INF/plugin.xml" ]]; then
465 + xml="$(tr '\n' ' ' <"$item/META-INF/plugin.xml")"
466 + elif [[ -f "$item" ]]; then
467 + xml="$(unzip -p "$item" META-INF/plugin.xml 2>/dev/null | tr '\n' ' ' || true)"
468 + elif [[ -d "$item" ]]; then
469 + while IFS= read -r -d '' archive; do
470 + xml="$(unzip -p "$archive" META-INF/plugin.xml 2>/dev/null | tr '\n' ' ' || true)"
471 + [[ -n "$xml" ]] && break
472 + done < <(find "$item" -maxdepth 3 -type f \( -name '*.jar' -o -name '*.zip' \) -print0 2>/dev/null)
473 + fi
474 + [[ -n "$xml" ]] || return 1
475 + id="$(xml_tag_value "$xml" id || true)"
476 + name="$(xml_tag_value "$xml" name || true)"
477 + [[ -n "$id" ]] || id="$name"
478 + [[ -n "$name" ]] || name="$id"
479 + [[ -n "$id" ]] || return 1
480 + printf '%s|%s\n' "$id" "$name"
481 + }
482 +
369 483 # ------------------------------------------------------------------
370 484 # IDE picker
371 485 # ------------------------------------------------------------------
@@ -395,20 +509,61 @@ if (( ${#SELECTED_IDES[@]} == 0 )); then
395 509 fi
396 510
397 511 # ------------------------------------------------------------------
398 - # Plugin picker
512 + # Action, then plugin picker
399 513 # ------------------------------------------------------------------
514 + ACTION="$(pick_action)"
515 +
400 516 declare -a plugin_lines=()
401 517 declare -A label_to_id
402 - for entry in "${PLUGIN_CATALOG[@]}"; do
403 - IFS='|' read -r pid plabel <<<"$entry"
404 - line="$(printf '%-32s %s' "$plabel" "$pid")"
405 - plugin_lines+=("$line")
406 - label_to_id[$line]="$pid"
407 - done
518 + declare -a INSTALLED_PLUGIN_RECORDS=()
519 +
520 + if [[ "$ACTION" == "install" ]]; then
521 + for entry in "${PLUGIN_CATALOG[@]}"; do
522 + IFS='|' read -r pid plabel <<<"$entry"
523 + line="$(printf '%-32s %s' "$plabel" "$pid")"
524 + plugin_lines+=("$line")
525 + label_to_id[$line]="$pid"
526 + done
527 + else
528 + if ! command -v unzip >/dev/null 2>&1; then
529 + echo "Uninstall requires 'unzip' so installed plugin IDs can be read safely." >&2
530 + exit 1
531 + fi
532 +
533 + declare -A seen_plugin_ids=()
534 + for ide_label in "${SELECTED_IDES[@]}"; do
535 + itype="${label_type[$ide_label]}"
536 + launcher="${label_launcher[$ide_label]}"
537 + if ! plugin_root="$(plugin_root_for_target "$launcher" "$itype")"; then
538 + echo "Could not resolve the plugin directory for: $ide_label" >&2
539 + continue
540 + fi
541 + [[ -d "$plugin_root" ]] || continue
542 +
543 + while IFS= read -r -d '' item; do
544 + metadata="$(plugin_metadata "$item" || true)"
545 + [[ -n "$metadata" ]] || continue
546 + IFS='|' read -r pid plabel <<<"$metadata"
547 + INSTALLED_PLUGIN_RECORDS+=("${pid}|${plabel}|${item}|${plugin_root}|${ide_label}")
548 + if [[ -z "${seen_plugin_ids[$pid]+x}" ]]; then
549 + line="$(printf '%-32s %s' "$plabel" "$pid")"
550 + plugin_lines+=("$line")
551 + label_to_id[$line]="$pid"
552 + seen_plugin_ids[$pid]=1
553 + fi
554 + done < <(find "$plugin_root" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
555 + done
556 +
557 + if (( ${#plugin_lines[@]} == 0 )); then
558 + echo "No removable user-installed plugins were found for the selected IDE(s)." >&2
559 + echo "Bundled plugins cannot be uninstalled; disable them in the IDE instead." >&2
560 + exit 0
561 + fi
562 + fi
408 563
409 564 mapfile -t SELECTED_PLUGIN_LINES < <(
410 565 printf '%s\n' "${plugin_lines[@]}" |
411 - pick_multi "Plugins>" "Pick plugin(s) — TAB to toggle (fzf) or spaced indexes"
566 + pick_multi "Plugins>" "Pick plugin(s) to ${ACTION} — TAB to toggle (fzf) or spaced indexes"
412 567 )
413 568
414 569 if (( ${#SELECTED_PLUGIN_LINES[@]} == 0 )); then
@@ -468,7 +623,7 @@ while :; do
468 623
469 624 {
470 625 echo
471 - echo "The following IDE(s) appear to be running — installPlugins needs them closed:"
626 + echo "The following IDE(s) appear to be running — plugin changes need them closed:"
472 627 for r in "${_running[@]}"; do
473 628 echo " - $r"
474 629 done
@@ -482,8 +637,64 @@ while :; do
482 637 done
483 638
484 639 # ------------------------------------------------------------------
485 - # Install loop
640 + # Install/uninstall loop
486 641 # ------------------------------------------------------------------
642 + if [[ "$ACTION" == "uninstall" ]]; then
643 + declare -A selected_id_map=()
644 + for pid in "${SELECTED_PLUGIN_IDS[@]}"; do selected_id_map[$pid]=1; done
645 +
646 + declare -a TO_REMOVE=()
647 + for record in "${INSTALLED_PLUGIN_RECORDS[@]}"; do
648 + IFS='|' read -r pid plabel plugin_path plugin_root ide_label <<<"$record"
649 + [[ -n "${selected_id_map[$pid]+x}" ]] || continue
650 + # Safety boundary: only top-level children found in the resolved plugin root.
651 + [[ "$(dirname "$plugin_path")" == "$plugin_root" ]] || continue
652 + TO_REMOVE+=("$record")
653 + done
654 +
655 + if (( ${#TO_REMOVE[@]} == 0 )); then
656 + echo "None of the selected plugins are installed in the selected IDE(s)." >&2
657 + exit 0
658 + fi
659 +
660 + echo
661 + echo "The following plugin installations will be removed:"
662 + for record in "${TO_REMOVE[@]}"; do
663 + IFS='|' read -r pid plabel plugin_path plugin_root ide_label <<<"$record"
664 + echo " - $plabel ($pid)"
665 + echo " IDE: $ide_label"
666 + echo " Path: $plugin_path"
667 + done
668 + printf 'Continue? [y/N]: ' >&2
669 + if ! IFS= read -r confirm </dev/tty || [[ ! "$confirm" =~ ^[Yy]$ ]]; then
670 + echo "Uninstall cancelled."
671 + exit 0
672 + fi
673 +
674 + removed=0
675 + failed=0
676 + declare -a FAILED_LIST=()
677 + for record in "${TO_REMOVE[@]}"; do
678 + IFS='|' read -r pid plabel plugin_path plugin_root ide_label <<<"$record"
679 + printf 'Removing %-32s from %s... ' "$plabel" "$ide_label"
680 + if rm -rf -- "$plugin_path"; then
681 + echo "ok"
682 + removed=$((removed + 1))
683 + else
684 + echo "FAILED"
685 + failed=$((failed + 1))
686 + FAILED_LIST+=("${ide_label} :: ${pid}")
687 + fi
688 + done
689 +
690 + echo "Done. Removed: ${removed}, Failed: ${failed}. Restart the IDE(s) to finish."
691 + if (( failed > 0 )); then
692 + printf ' - %s\n' "${FAILED_LIST[@]}" >&2
693 + exit 1
694 + fi
695 + exit 0
696 + fi
697 +
487 698 LOG_FILE="$(mktemp -t jb-install.XXXXXX)"
488 699 trap 'rm -f "$LOG_FILE"' EXIT
489 700
@@ -556,4 +767,4 @@ if (( failed > 0 )); then
556 767 echo " - $f" >&2
557 768 done
558 769 exit 1
559 - fi
770 + fi

weehong a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 1 insertion, 1 deletion

ideavimrc

@@ -34,7 +34,7 @@ Plug 'terryma/vim-multiple-cursors'
34 34
35 35 " --- Better IdeaVim / IDE integration ---
36 36 set ideajoin
37 - set ideaput
37 + set clipboard+=unnamedplus
38 38 set ideamarks
39 39
40 40 " --- Optional built-in IdeaVim extensions ---

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 270 insertions

ideavim-cheatsheet.html(fichier créé)

@@ -0,0 +1,270 @@
1 + <!doctype html>
2 + <html lang="en">
3 + <head>
4 + <meta charset="utf-8">
5 + <meta name="viewport" content="width=device-width, initial-scale=1">
6 + <title>IdeaVim Cheatsheet</title>
7 + <style>
8 + @page {
9 + size: A4;
10 + margin: 14mm;
11 + }
12 +
13 + * {
14 + box-sizing: border-box;
15 + }
16 +
17 + body {
18 + margin: 0;
19 + color: #15171a;
20 + background: #ffffff;
21 + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
22 + font-size: 10.5px;
23 + line-height: 1.35;
24 + }
25 +
26 + header {
27 + margin-bottom: 14px;
28 + padding-bottom: 10px;
29 + border-bottom: 2px solid #15171a;
30 + }
31 +
32 + h1 {
33 + margin: 0 0 3px;
34 + font-size: 25px;
35 + line-height: 1.1;
36 + letter-spacing: 0;
37 + }
38 +
39 + .subtitle {
40 + margin: 0;
41 + color: #4d5662;
42 + font-size: 11px;
43 + }
44 +
45 + .grid {
46 + display: grid;
47 + grid-template-columns: 1fr 1fr;
48 + gap: 11px 14px;
49 + align-items: start;
50 + }
51 +
52 + section {
53 + break-inside: avoid;
54 + page-break-inside: avoid;
55 + }
56 +
57 + h2 {
58 + margin: 0 0 5px;
59 + padding: 5px 7px;
60 + color: #ffffff;
61 + background: #243447;
62 + border-radius: 4px;
63 + font-size: 11px;
64 + line-height: 1.2;
65 + letter-spacing: 0;
66 + }
67 +
68 + table {
69 + width: 100%;
70 + border-collapse: collapse;
71 + table-layout: fixed;
72 + border: 1px solid #d5dbe3;
73 + border-radius: 4px;
74 + overflow: hidden;
75 + }
76 +
77 + th,
78 + td {
79 + padding: 5px 6px;
80 + vertical-align: top;
81 + border-bottom: 1px solid #e5e9ef;
82 + }
83 +
84 + tr:last-child td {
85 + border-bottom: 0;
86 + }
87 +
88 + th {
89 + color: #4d5662;
90 + background: #f4f6f8;
91 + font-weight: 650;
92 + text-align: left;
93 + font-size: 9px;
94 + text-transform: uppercase;
95 + }
96 +
97 + th:first-child,
98 + td:first-child {
99 + width: 33%;
100 + }
101 +
102 + code {
103 + display: inline-block;
104 + padding: 1px 4px;
105 + color: #0f1720;
106 + background: #eef2f7;
107 + border: 1px solid #dce3ec;
108 + border-radius: 3px;
109 + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
110 + font-size: 9.5px;
111 + line-height: 1.25;
112 + white-space: nowrap;
113 + }
114 +
115 + .note {
116 + margin: 10px 0 0;
117 + padding: 7px 8px;
118 + color: #313942;
119 + background: #fff7e1;
120 + border: 1px solid #ead48a;
121 + border-radius: 4px;
122 + font-size: 10px;
123 + }
124 +
125 + .wide {
126 + grid-column: 1 / -1;
127 + }
128 + </style>
129 + </head>
130 + <body>
131 + <header>
132 + <h1>IdeaVim Cheatsheet</h1>
133 + <p class="subtitle">Leader key: <code>Space</code>. Example: <code>&lt;leader&gt;ff</code> means <code>Space f f</code>.</p>
134 + </header>
135 +
136 + <main class="grid">
137 + <section>
138 + <h2>Navigation</h2>
139 + <table>
140 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
141 + <tbody>
142 + <tr><td><code>gd</code></td><td>Go to declaration</td></tr>
143 + <tr><td><code>gi</code></td><td>Go to implementation</td></tr>
144 + <tr><td><code>gr</code></td><td>Find usages</td></tr>
145 + <tr><td><code>gb</code></td><td>Go back</td></tr>
146 + <tr><td><code>gB</code></td><td>Go forward</td></tr>
147 + </tbody>
148 + </table>
149 + </section>
150 +
151 + <section>
152 + <h2>Files And Search</h2>
153 + <table>
154 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
155 + <tbody>
156 + <tr><td><code>Space ff</code></td><td>Go to file</td></tr>
157 + <tr><td><code>Space fs</code></td><td>Find in project</td></tr>
158 + <tr><td><code>Space fa</code></td><td>Go to IDE action</td></tr>
159 + <tr><td><code>Space fr</code></td><td>Recent files</td></tr>
160 + <tr><td><code>Space e</code></td><td>Select current file in project view</td></tr>
161 + </tbody>
162 + </table>
163 + </section>
164 +
165 + <section>
166 + <h2>Code Actions</h2>
167 + <table>
168 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
169 + <tbody>
170 + <tr><td><code>Space ca</code></td><td>Show intention actions / quick fix</td></tr>
171 + <tr><td><code>Space cf</code></td><td>Reformat code</td></tr>
172 + <tr><td><code>Space co</code></td><td>Optimize imports</td></tr>
173 + <tr><td><code>Space R</code></td><td>Rename symbol</td></tr>
174 + </tbody>
175 + </table>
176 + </section>
177 +
178 + <section>
179 + <h2>Run And Debug</h2>
180 + <table>
181 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
182 + <tbody>
183 + <tr><td><code>Space rr</code></td><td>Run</td></tr>
184 + <tr><td><code>Space dd</code></td><td>Debug</td></tr>
185 + <tr><td><code>Space db</code></td><td>Toggle breakpoint</td></tr>
186 + </tbody>
187 + </table>
188 + </section>
189 +
190 + <section>
191 + <h2>Tool Windows</h2>
192 + <table>
193 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
194 + <tbody>
195 + <tr><td><code>Space tt</code></td><td>Open terminal</td></tr>
196 + <tr><td><code>Space hh</code></td><td>Hide all tool windows</td></tr>
197 + </tbody>
198 + </table>
199 + </section>
200 +
201 + <section>
202 + <h2>Completion</h2>
203 + <table>
204 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
205 + <tbody>
206 + <tr><td><code>Tab</code></td><td>Next completion item</td></tr>
207 + <tr><td><code>Shift+Tab</code></td><td>Previous completion item</td></tr>
208 + </tbody>
209 + </table>
210 + </section>
211 +
212 + <section class="wide">
213 + <h2>Surround</h2>
214 + <table>
215 + <thead><tr><th>Key</th><th>Example / Meaning</th></tr></thead>
216 + <tbody>
217 + <tr><td><code>cs"'</code></td><td>Change <code>"hello"</code> to <code>'hello'</code></td></tr>
218 + <tr><td><code>cs({</code></td><td>Change <code>(x)</code> to <code>{ x }</code></td></tr>
219 + <tr><td><code>ds"</code></td><td>Delete surrounding quotes from <code>"hello"</code></td></tr>
220 + <tr><td><code>ysiw"</code></td><td>Surround current word with quotes</td></tr>
221 + <tr><td><code>yss)</code></td><td>Surround the whole line with parentheses</td></tr>
222 + </tbody>
223 + </table>
224 + </section>
225 +
226 + <section>
227 + <h2>Text Objects</h2>
228 + <table>
229 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
230 + <tbody>
231 + <tr><td><code>ciq</code></td><td>Change inside any quote</td></tr>
232 + <tr><td><code>caq</code></td><td>Change around any quote</td></tr>
233 + <tr><td><code>cib</code></td><td>Change inside any bracket</td></tr>
234 + <tr><td><code>cab</code></td><td>Change around any bracket</td></tr>
235 + <tr><td><code>diq</code></td><td>Delete inside any quote</td></tr>
236 + <tr><td><code>dib</code></td><td>Delete inside any bracket</td></tr>
237 + <tr><td><code>viq</code></td><td>Select inside any quote</td></tr>
238 + <tr><td><code>vib</code></td><td>Select inside any bracket</td></tr>
239 + </tbody>
240 + </table>
241 + </section>
242 +
243 + <section>
244 + <h2>Multi-Cursor</h2>
245 + <table>
246 + <thead><tr><th>Key</th><th>Action</th></tr></thead>
247 + <tbody>
248 + <tr><td><code>Ctrl+n</code></td><td>Select next occurrence of word</td></tr>
249 + <tr><td><code>g Ctrl+n</code></td><td>Select next occurrence, not whole word</td></tr>
250 + <tr><td><code>Alt+n</code></td><td>Select all occurrences in file</td></tr>
251 + <tr><td><code>Ctrl+x</code></td><td>Skip current occurrence</td></tr>
252 + <tr><td><code>Ctrl+p</code></td><td>Remove current occurrence</td></tr>
253 + </tbody>
254 + </table>
255 + </section>
256 +
257 + <section class="wide">
258 + <h2>Reload Config</h2>
259 + <table>
260 + <thead><tr><th>Key / Command</th><th>Action</th></tr></thead>
261 + <tbody>
262 + <tr><td><code>Ctrl+Shift+O</code></td><td>Reload <code>~/.ideavimrc</code> from JetBrains</td></tr>
263 + <tr><td><code>:source ~/.ideavimrc</code></td><td>Reload <code>~/.ideavimrc</code> from Vim command mode</td></tr>
264 + </tbody>
265 + </table>
266 + <p class="note">EasyMotion requires the JetBrains IdeaVim-EasyMotion and AceJump IDE plugins.</p>
267 + </section>
268 + </main>
269 + </body>
270 + </html>

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 0 insertions, 0 deletions

.ideavimrc renommé en ideavimrc

Fichier renommé sans modifications

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 82 insertions

.ideavimrc(fichier créé)

@@ -0,0 +1,82 @@
1 + " .ideavimrc is a configuration file for IdeaVim plugin. It uses
2 + " the same commands as the original .vimrc configuration.
3 + " You can find a list of commands here: https://jb.gg/h38q75
4 + " Find more examples here: https://jb.gg/share-ideavimrc
5 +
6 + " Source your .vimrc
7 + source ~/.vimrc
8 +
9 + "" -- Suggested options --
10 + " Show a few lines of context around the cursor. Note that this makes the
11 + " text scroll if you mouse-click near the start or end of the window.
12 + set scrolloff=5
13 +
14 + " Do incremental searching.
15 + set incsearch
16 +
17 + " Don't use Ex mode, use Q for formatting.
18 + map Q gq
19 +
20 + " --- Enable IdeaVim plugins https://jb.gg/ideavim-plugins
21 +
22 + " Highlight copied text
23 + Plug 'machakann/vim-highlightedyank'
24 + " Commentary plugin
25 + Plug 'tpope/vim-commentary'
26 + " Surround text objects and changes
27 + Plug 'tpope/vim-surround'
28 + " Better text objects around quotes, brackets, arguments, and separators
29 + Plug 'wellle/targets.vim'
30 + " Fast cursor jumps. Requires the IdeaVim-EasyMotion and AceJump IDE plugins.
31 + Plug 'easymotion/vim-easymotion'
32 + " Multi-cursor editing
33 + Plug 'terryma/vim-multiple-cursors'
34 +
35 + " --- Better IdeaVim / IDE integration ---
36 + set ideajoin
37 + set ideaput
38 + set ideamarks
39 +
40 + " --- Optional built-in IdeaVim extensions ---
41 + set mini-ai
42 + set youcompleteme
43 +
44 + " --- IDE navigation ---
45 + nmap gd <Action>(GotoDeclaration)
46 + nmap gi <Action>(GotoImplementation)
47 + nmap gr <Action>(FindUsages)
48 + nmap gb <Action>(Back)
49 + nmap gB <Action>(Forward)
50 +
51 + " --- Search / files / project ---
52 + nmap <leader>ff <Action>(GotoFile)
53 + nmap <leader>fs <Action>(FindInPath)
54 + nmap <leader>fa <Action>(GotoAction)
55 + nmap <leader>fr <Action>(RecentFiles)
56 + nmap <leader>e <Action>(SelectInProjectView)
57 +
58 + " --- Code actions ---
59 + nmap <leader>ca <Action>(ShowIntentionActions)
60 + nmap <leader>cf <Action>(ReformatCode)
61 + nmap <leader>co <Action>(OptimizeImports)
62 + nmap <leader>R <Action>(RenameElement)
63 +
64 + " --- Run / debug ---
65 + nmap <leader>rr <Action>(Run)
66 + nmap <leader>dd <Action>(Debug)
67 + nmap <leader>db <Action>(ToggleLineBreakpoint)
68 +
69 + " --- Tool windows ---
70 + nmap <leader>tt <Action>(ActivateTerminalToolWindow)
71 + nmap <leader>hh <Action>(HideAllWindows)
72 +
73 +
74 + "" -- Map IDE actions to IdeaVim -- https://jb.gg/abva4t
75 + "" Map \r to the Reformat Code action
76 + "map \r <Action>(ReformatCode)
77 +
78 + "" Map <leader>d to start debug
79 + "map <leader>d <Action>(Debug)
80 +
81 + "" Map \b to toggle the breakpoint on the current line
82 + "map \b <Action>(ToggleLineBreakpoint)

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 17 insertions, 9 deletions

install.sh

@@ -120,7 +120,10 @@ fi
120 120
121 121 # 4. Extract extensions from JSON using jq (if not using hardcoded array)
122 122 if [[ "$USE_HARDCODED" == false ]]; then
123 - mapfile -t EXTENSIONS < <(jq -r '.recommendations[]?' "$JSON_FILE")
123 + EXTENSIONS=()
124 + while IFS= read -r ext; do
125 + EXTENSIONS+=("$ext")
126 + done < <(jq -r '.recommendations[]?' "$JSON_FILE")
124 127
125 128 if [[ ${#EXTENSIONS[@]} -eq 0 ]]; then
126 129 echo "Error: No extensions found under the 'recommendations' key." >&2
@@ -128,14 +131,19 @@ if [[ "$USE_HARDCODED" == false ]]; then
128 131 fi
129 132 fi
130 133
131 - declare -a CODE_ARGS=()
132 - if [[ -n "$PROFILE" ]]; then
133 - CODE_ARGS+=("--profile" "$PROFILE")
134 - fi
135 -
136 134 # Trap to clean up logs and temporary downloaded JSON files
137 135 LOG_FILE="$(mktemp -t vscode-install.XXXXXX)"
138 - trap 'rm -f "$LOG_FILE" $TEMP_JSON_FILE' EXIT
136 + trap 'rm -f "$LOG_FILE" "${TEMP_JSON_FILE:-}"' EXIT
137 +
138 + install_extension() {
139 + local ext="$1"
140 +
141 + if [[ -n "$PROFILE" ]]; then
142 + code --profile "$PROFILE" --install-extension "$ext" --force
143 + else
144 + code --install-extension "$ext" --force
145 + fi
146 + }
139 147
140 148 header="Installing ${#EXTENSIONS[@]} extension(s)"
141 149 [[ -n "$PROFILE" ]] && header+=" into profile '$PROFILE'"
@@ -150,7 +158,7 @@ for ext in "${EXTENSIONS[@]}"; do
150 158 [[ -z "$ext" ]] && continue
151 159
152 160 printf ' -> %-55s ' "$ext"
153 - if code "${CODE_ARGS[@]}" --install-extension "$ext" --force >"$LOG_FILE" 2>&1; then
161 + if install_extension "$ext" >"$LOG_FILE" 2>&1; then
154 162 echo "ok"
155 163 installed=$((installed + 1))
156 164 else
@@ -170,4 +178,4 @@ if (( failed > 0 )); then
170 178 echo " - $f" >&2
171 179 done
172 180 exit 1
173 - fi
181 + fi
Plus récent Plus ancien