<# Merge curated defaults into the default VS Code profile settings. #> [CmdletBinding()] param() $ErrorActionPreference = 'Stop' function ConvertFrom-JsoncText { param([Parameter(Mandatory = $true)][string]$Text) $withoutComments = [System.Text.StringBuilder]::new() $state = 'Normal' $escaped = $false for ($i = 0; $i -lt $Text.Length; $i++) { $char = $Text[$i] $next = if ($i + 1 -lt $Text.Length) { $Text[$i + 1] } else { [char]0 } switch ($state) { 'Normal' { if ($char -eq '"') { [void]$withoutComments.Append($char) $state = 'String' } elseif ($char -eq '/' -and $next -eq '/') { $state = 'LineComment' $i++ } elseif ($char -eq '/' -and $next -eq '*') { $state = 'BlockComment' $i++ } else { [void]$withoutComments.Append($char) } } 'String' { [void]$withoutComments.Append($char) if ($escaped) { $escaped = $false } elseif ($char -eq '\') { $escaped = $true } elseif ($char -eq '"') { $state = 'Normal' } } 'LineComment' { if ($char -eq "`r" -or $char -eq "`n") { [void]$withoutComments.Append($char) $state = 'Normal' } } 'BlockComment' { if ($char -eq '*' -and $next -eq '/') { $state = 'Normal' $i++ } } } } if ($state -eq 'BlockComment') { throw 'settings.json contains an unterminated block comment.' } $clean = $withoutComments.ToString() $withoutTrailingCommas = [System.Text.StringBuilder]::new() $state = 'Normal' $escaped = $false for ($i = 0; $i -lt $clean.Length; $i++) { $char = $clean[$i] if ($state -eq 'String') { [void]$withoutTrailingCommas.Append($char) if ($escaped) { $escaped = $false } elseif ($char -eq '\') { $escaped = $true } elseif ($char -eq '"') { $state = 'Normal' } continue } if ($char -eq '"') { [void]$withoutTrailingCommas.Append($char) $state = 'String' continue } if ($char -eq ',') { $lookAhead = $i + 1 while ($lookAhead -lt $clean.Length -and [char]::IsWhiteSpace($clean[$lookAhead])) { $lookAhead++ } if ($lookAhead -lt $clean.Length -and ($clean[$lookAhead] -eq '}' -or $clean[$lookAhead] -eq ']')) { continue } } [void]$withoutTrailingCommas.Append($char) } $json = $withoutTrailingCommas.ToString() if ([string]::IsNullOrWhiteSpace($json)) { $json = '{}' } return $json | ConvertFrom-Json } $settingsDir = if ($env:VSCODE_SETTINGS_DIR) { $env:VSCODE_SETTINGS_DIR } else { Join-Path $env:APPDATA 'Code\User' } $settingsFile = Join-Path $settingsDir 'settings.json' [void](New-Item -ItemType Directory -Path $settingsDir -Force) $backupFile = $null if (Test-Path -LiteralPath $settingsFile -PathType Leaf) { $timestamp = Get-Date -Format 'yyyyMMddHHmmssfff' $backupFile = "$settingsFile.backup.$timestamp" Copy-Item -LiteralPath $settingsFile -Destination $backupFile Write-Host "Backup created: $backupFile" $settings = ConvertFrom-JsoncText -Text ([System.IO.File]::ReadAllText($settingsFile)) if ($settings -isnot [System.Management.Automation.PSCustomObject]) { throw 'settings.json must contain a JSON object.' } } else { $settings = [pscustomobject]@{} } $managedSettings = [ordered]@{ 'editor.fontFamily' = "'Ubuntu Mono', 'IBM Plex Mono', 'JetBrains Mono', Hack, MonoLisa, monospace" 'editor.formatOnSave' = $true 'editor.lineHeight' = 24 'editor.renderWhitespace' = 'all' 'editor.rulers' = @(80, 100, 120) 'editor.wordWrap' = 'wordWrapColumn' 'editor.wordWrapColumn' = 80 'workbench.colorTheme' = 'GitHub Dark Default' 'workbench.iconTheme' = 'material-icon-theme' } foreach ($entry in $managedSettings.GetEnumerator()) { $settings | Add-Member -NotePropertyName $entry.Key -NotePropertyValue $entry.Value -Force } $tempFile = Join-Path $settingsDir ("settings.json.tmp.{0}" -f [guid]::NewGuid().ToString('N')) try { $json = $settings | ConvertTo-Json -Depth 100 [System.IO.File]::WriteAllText($tempFile, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false)) Move-Item -LiteralPath $tempFile -Destination $settingsFile -Force } catch { Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue if ($backupFile) { Write-Error "Could not merge settings. Original preserved at: $backupFile" } throw } Write-Host "VS Code settings updated: $settingsFile" -ForegroundColor Green