diff --git a/Microsoft.PowerShell_profile.ps1 b/Microsoft.PowerShell_profile.ps1 index 8be5562..5caa34f 100644 --- a/Microsoft.PowerShell_profile.ps1 +++ b/Microsoft.PowerShell_profile.ps1 @@ -208,9 +208,14 @@ function Get-Utf8FileText { [System.IO.File]::ReadAllText($Path) } -# Write UTF-8 without BOM through a sibling temp file before replacing the target. +# Write UTF-8 without BOM through a sibling temp file before replacing the target; a symlink is written in place so the link survives. function Write-Utf8FileAtomic { param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][AllowEmptyString()][string]$Content) + $existing = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue + if ($existing -and $existing.LinkType) { + [System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($false)) + return + } $dir = Split-Path -Parent $Path if (-not $dir) { $dir = '.' } $tmp = Join-Path $dir ('.psp-tmp-' + [System.IO.Path]::GetRandomFileName()) @@ -951,12 +956,15 @@ function Update-Profile { } # Apply user-settings.json overrides (never downloaded, never overwritten) + $wtManaged = $true if (Test-Path $userSettingsPath) { try { $userSettings = Get-Utf8FileText $userSettingsPath | ConvertFrom-Json $userSettingsParsed = $true $userThemeOverridePresent = $null -ne $userSettings.theme $userWindowsTerminalOverridePresent = $null -ne $userSettings.windowsTerminal + # windowsTerminal.manage = false hands settings.json to another tool, such as a dotfiles repo. + if ($userSettings.windowsTerminal -and $userSettings.windowsTerminal.PSObject.Properties['manage'] -and $userSettings.windowsTerminal.manage -eq $false) { $wtManaged = $false } $userTerminalDefaultsOverridePresent = $null -ne $userSettings.defaults $userKeybindingsOverridePresent = $null -ne $userSettings.keybindings if ($config -and $userSettings.theme) { @@ -996,7 +1004,8 @@ function Update-Profile { "_comment": "User overrides for terminal, theme, and profile behavior. Only add keys you want to override.", "_examples": { "theme": { "name": "catppuccin", "url": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/catppuccin.omp.json" }, - "windowsTerminal": { "colorScheme": "One Half Dark", "cursorColor": "#ffffff" }, + "windowsTerminal": { "manage": true, "colorScheme": "One Half Dark", "cursorColor": "#ffffff" }, + "_manage_note": "set windowsTerminal.manage to false when another tool (for example a dotfiles repo) owns Windows Terminal settings.json; the profile then leaves that file alone", "defaults": { "opacity": 90, "font": { "size": 14 }, @@ -1123,7 +1132,10 @@ function Update-Profile { # Phase 6: Synchronize every installed Windows Terminal variant. $terminalOverridesChanged = $userSettingsChanged -and ($userWindowsTerminalOverridePresent -or $userTerminalDefaultsOverridePresent -or $userKeybindingsOverridePresent) - if (($Force -or $profileChanged -or $configChanged -or $terminalConfigChanged -or $terminalOverridesChanged) -and (($config -and $config.windowsTerminal) -or $terminalConfig)) { + if (-not $wtManaged) { + Write-Host "Windows Terminal settings left alone (windowsTerminal.manage = false in user-settings.json)." -ForegroundColor DarkGray + } + elseif (($Force -or $profileChanged -or $configChanged -or $terminalConfigChanged -or $terminalOverridesChanged) -and (($config -and $config.windowsTerminal) -or $terminalConfig)) { $wtSettingsPaths = Get-WindowsTerminalSettingsPaths foreach ($wtSettingsPath in $wtSettingsPaths) { if ($PSCmdlet.ShouldProcess($wtSettingsPath, "Update Windows Terminal settings")) { @@ -2895,6 +2907,36 @@ function Test-ProfileHealth { $results += [pscustomobject]@{ Category = 'Config'; Check = 'user-settings.json'; Status = 'WARN'; Detail = 'missing (no overrides applied)' } } + # Windows Terminal settings.json ownership: a symlink or windowsTerminal.manage = false means another tool owns it. + $wtManagedHere = $true + if (Test-Path $us) { + try { + $usCfg = Get-Content $us -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop + if ($usCfg.windowsTerminal -and $usCfg.windowsTerminal.PSObject.Properties['manage'] -and $usCfg.windowsTerminal.manage -eq $false) { $wtManagedHere = $false } + } + catch { $null = $_ } + } + foreach ($wtPath in @(Get-WindowsTerminalSettingsPaths)) { + $wtItem = Get-Item -LiteralPath $wtPath -Force -ErrorAction SilentlyContinue + if (-not $wtItem) { continue } + $wtVariant = if ($wtPath -match 'WindowsTerminalPreview') { 'Preview' } elseif ($wtPath -match 'WindowsTerminalCanary') { 'Canary' } elseif ($wtPath -match 'Packages') { 'Stable' } else { 'Unpackaged' } + if ($wtItem.LinkType) { + $wtTarget = [string](@($wtItem.Target)[0]) + if ($wtManagedHere) { + $results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'WARN'; Detail = "symlink to $wtTarget; set windowsTerminal.manage = false in user-settings.json if that tool owns it" } + } + else { + $results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'OK'; Detail = "symlink to $wtTarget, managed elsewhere" } + } + } + elseif ($wtManagedHere) { + $results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'OK'; Detail = 'managed by this profile' } + } + else { + $results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'OK'; Detail = 'not managed (windowsTerminal.manage = false)' } + } + } + # Check the font this install actually uses: a wizard choice in user-settings.json # overrides the shipped terminal-config.json default. $fontName = $null @@ -3160,7 +3202,11 @@ function Uninstall-Profile { $backups = Get-ChildItem -Path $wtLocalState -Filter 'settings.json.*.bak' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending - if ($HardResetWindowsTerminal) { + $wtItem = Get-Item -LiteralPath $wtSettingsPath -Force -ErrorAction SilentlyContinue + if ($wtItem -and $wtItem.LinkType) { + Write-Host " $wtSettingsPath is a symlink managed elsewhere; Windows Terminal settings left in place." -ForegroundColor DarkGray + } + elseif ($HardResetWindowsTerminal) { if (Test-Path $wtSettingsPath) { if ($PSCmdlet.ShouldProcess($wtSettingsPath, 'Delete WT settings for hard reset')) { Remove-Item $wtSettingsPath -Force -ErrorAction SilentlyContinue @@ -6089,6 +6135,10 @@ function Set-TerminalBackground { } # 2. Apply the background to every installed Windows Terminal variant. + if ($settings.PSObject.Properties['windowsTerminal'] -and $settings.windowsTerminal.PSObject.Properties['manage'] -and $settings.windowsTerminal.manage -eq $false) { + Write-Host 'Windows Terminal settings.json is managed elsewhere (windowsTerminal.manage = false). Background persisted to user-settings.json only.' -ForegroundColor DarkGray + return + } $wtSettingsPaths = Get-WindowsTerminalSettingsPaths if (-not $wtSettingsPaths -or $wtSettingsPaths.Count -eq 0) { Write-Host 'Windows Terminal settings.json not found (Store/Preview/Canary/unpackaged). Change persisted; will apply after next Update-Profile.' -ForegroundColor DarkGray diff --git a/README.md b/README.md index ece7b61..7545cc1 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Reconfigure-Profile 4. **Nerd Font** — Caskaydia, JetBrainsMono, FiraCode, Meslo, Hack, or Iosevka. Fetches latest release tag from [ryanoasis/nerd-fonts](https://github.com/ryanoasis/nerd-fonts/releases) automatically. 5. **Tab bar color + window chrome** — presets (scheme-match, pure black, custom hex) + `applicationTheme` dark/light. 6. **Terminal appearance** — opacity, `useAcrylic`, font size, cursor shape, padding, scrollbar state, history size. Each prompt keeps the current default on Enter. -7. **PSReadLine colors** — default / derive from chosen scheme / skip. +7. **PSReadLine colors** — derive from chosen scheme (default) / shipped palette / skip. 8. **Background image** — optional path + opacity (0.05–0.50). Skipped by default. 9. **Editor preference** — VS Code, Notepad++, Neovim, Vim, Notepad, or a custom exe. Used by `edit`, `ep`, `hosts`, etc. 10. **Telemetry opt-out + feature toggles** — `psfzf`, `predictions`, `startupMessage`, `perDirProfiles`, `commandOverrides` — y/n per item with sensible defaults. @@ -160,6 +160,7 @@ Four extension points survive updates. From simplest to most powerful: - **`user-settings.json`** (`%LOCALAPPDATA%\PowerShellProfile\`) - JSON overrides. Keys: - `theme`, `windowsTerminal`, `defaults`, `keybindings` - terminal and OMP theme + - `windowsTerminal.manage` - set to `false` when another tool owns Windows Terminal's `settings.json` (a dotfiles repo, for example). `setup.ps1`, `Update-Profile`, `Set-TerminalBackground` and `Uninstall-Profile` then leave that file alone, and `psp-doctor` reports who owns it. A symlinked `settings.json` is always written in place, so the link survives updates. - `defaults.backgroundImage` / `backgroundImageOpacity` / `backgroundImageStretchMode` / `backgroundImageAlignment` - Windows Terminal background image - `features` - toggle heavy/optional behavior: `psfzf`, `predictions`, `startupMessage`, `perDirProfiles` (all `true` by default), `transientPrompt` (collapses previous prompt on Enter; default `false`, customize via `$script:PSP.TransientPrompt = { ... }` in `profile_user.ps1`), `updateCheck` (notifies once a week when main has advanced past the applied commit; default `false` so `irm | iex` in scripts does not trigger a surprise network call) - `commandOverrides` - redefine any command without editing source: `{ "gs": "git status --short" }`. Opt-in: set `features.commandOverrides = true` in the same file. Default off because it compiles JSON strings into executable scriptblocks. @@ -453,6 +454,7 @@ PowerShellPerfect bundles a **prompt** (via Oh My Posh), a **command suite**, an | `running scripts is disabled on this system` | Run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. (The recommended one-liner runs an in-memory scriptblock, which isn't subject to the script-file execution policy.) | | Prompt shows boxes / missing glyphs | The terminal font isn't a Nerd Font. Set your Windows Terminal profile font to the one setup installed (e.g. *CaskaydiaCove Nerd Font*) and restart WT. | | `oh-my-posh` not found right after install | Reopen the terminal (PATH refresh) or run `Update-SessionPathFromRegistry`; confirm with `psp-doctor`. | +| Windows Terminal settings keep changing back | Another tool also writes `settings.json`. Pick one owner: set `windowsTerminal.manage` to `false` in `user-settings.json` to let the other tool have it, or stop the other tool. `psp-doctor` shows whether the file is a symlink and who manages it. | | Turned a feature off in the wizard but it still loads | Check the `features` block in `user-settings.json`, then restart PowerShell. | | Something feels off | Run `psp-doctor` (alias for `Test-ProfileHealth`): OK/WARN/FAIL per check across tools, caches, fonts, PATH, and modules. | diff --git a/setup.ps1 b/setup.ps1 index bc9a04e..5da759a 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -634,7 +634,7 @@ function Start-InstallWizard { @{ Name = 'Derive from color scheme'; Desc = 'Map scheme ANSI roles to syntax (Command, String, ...)'; Value = 'scheme' } @{ Name = 'Skip'; Desc = 'No override; edit user-settings.json later'; Value = $null } ) - $pick = Select-WizardItem -Title 'PSReadLine syntax colors' -Items $rlOptions -DefaultName 'theme.json default' + $pick = Select-WizardItem -Title 'PSReadLine syntax colors' -Items $rlOptions -DefaultName 'Derive from color scheme' if ($pick -and $pick.Value) { $choices.PSReadLine = $pick.Value } $choices.CompletedSteps += 'PSReadLine' Save-State @@ -1149,10 +1149,13 @@ function Merge-JsonObject { } # Apply user-settings.json overrides (never downloaded, never overwritten) +$script:WtManaged = $true $userSettingsPath = Join-Path $configCachePath "user-settings.json" if (Test-Path $userSettingsPath) { try { $userSettings = Get-Content $userSettingsPath -Raw | ConvertFrom-Json + # windowsTerminal.manage = false hands settings.json to another tool, such as a dotfiles repo. + if ($userSettings.windowsTerminal -and $userSettings.windowsTerminal.PSObject.Properties['manage'] -and $userSettings.windowsTerminal.manage -eq $false) { $script:WtManaged = $false } if ($profileConfig -and $userSettings.theme) { if (-not $profileConfig.theme) { $profileConfig | Add-Member -NotePropertyName "theme" -NotePropertyValue ([PSCustomObject]@{}) -Force @@ -1272,7 +1275,8 @@ if (-not (Test-Path $userSettingsTemplate)) { "_comment": "User overrides for terminal, theme, and profile behavior. Only add keys you want to override.", "_examples": { "theme": { "name": "catppuccin", "url": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/catppuccin.omp.json" }, - "windowsTerminal": { "colorScheme": "One Half Dark", "cursorColor": "#ffffff" }, + "windowsTerminal": { "manage": true, "colorScheme": "One Half Dark", "cursorColor": "#ffffff" }, + "_manage_note": "set windowsTerminal.manage to false when another tool (for example a dotfiles repo) owns Windows Terminal settings.json; the profile then leaves that file alone", "defaults": { "opacity": 90, "font": { "size": 14 }, @@ -1575,7 +1579,11 @@ else { # Merge font, theme, and appearance into every Windows Terminal variant. Write-Host "[10/10] Windows Terminal" -ForegroundColor Cyan $wtSettingsPaths = Get-WindowsTerminalSettingsPaths -if (-not $wtSettingsPaths -or $wtSettingsPaths.Count -eq 0) { +if (-not $script:WtManaged) { + Write-Host " Skipped: user-settings.json sets windowsTerminal.manage = false, so settings.json is left to the tool that owns it." -ForegroundColor DarkGray + $wtSettingsPaths = @() +} +elseif (-not $wtSettingsPaths -or $wtSettingsPaths.Count -eq 0) { Write-Host " Windows Terminal settings not found (skipped)." -ForegroundColor Yellow } foreach ($wtSettingsPath in $wtSettingsPaths) {