src/GitPrompt.ps1
|
#requires -Version 5.1 # GitPrompt.ps1 - Prompt formatting for power-git # # NOTE: No custom class type constraints are used as parameters here. # PS 5.1 class/enum type tables are per-file; cross-file type references in # parameter constraints fail at runtime. Duck typing is used instead. # ────────────────────────────────────────────────────────────────────────────── # Internal rendering helpers # ────────────────────────────────────────────────────────────────────────────── function Write-AnsiColor { param($Builder, $Color, [string]$Text) if ($Text) { $null = $Builder.Append($Color.Ansi) $null = $Builder.Append($Text) } } function Write-LegacyColor { param($Color, [string]$Text) if ($Text) { Write-Host -NoNewline -ForegroundColor $Color.Legacy $Text } } # ────────────────────────────────────────────────────────────────────────────── # Branch name rendering # ────────────────────────────────────────────────────────────────────────────── function Format-BranchName { param($Branch, $Settings) $sym = if ($Branch.IsDetached) { $Settings.Symbols.Detached } else { $Settings.Symbols.Branch } $name = $Branch.Name if ($Settings.BranchMaxLength -gt 0 -and $name.Length -gt $Settings.BranchMaxLength) { $keep = [Math]::Max($Settings.BranchMaxLength - 1, 3) $name = $name.Substring(0, $keep) + [char]0x2026 # ellipsis } if ($sym) { return "$sym $name" } return $name } # ────────────────────────────────────────────────────────────────────────────── # Upstream tracking symbol # ────────────────────────────────────────────────────────────────────────────── function Get-BranchStatusSymbol { param($Branch, $Settings) if (-not $Branch.Upstream) { return $null } if ($Branch.UpstreamGone) { return $Settings.Symbols.Gone } if ($Branch.AheadBy -gt 0 -and $Branch.BehindBy -gt 0) { return "$($Settings.Symbols.Diverged) +$($Branch.AheadBy) -$($Branch.BehindBy)" } if ($Branch.AheadBy -gt 0) { return "$($Settings.Symbols.Ahead)$($Branch.AheadBy)" } if ($Branch.BehindBy -gt 0) { return "$($Settings.Symbols.Behind)$($Branch.BehindBy)" } return $Settings.Symbols.UpToDate } function Get-BranchStatusColor { param($Branch, $Settings) if (-not $Branch.Upstream) { return $Settings.BranchColor } if ($Branch.UpstreamGone) { return $Settings.BranchGoneColor } if ($Branch.AheadBy -gt 0 -and $Branch.BehindBy -gt 0) { return $Settings.BranchDivergedColor } if ($Branch.AheadBy -gt 0) { return $Settings.BranchAheadColor } if ($Branch.BehindBy -gt 0) { return $Settings.BranchBehindColor } return $Settings.BranchColor } # ────────────────────────────────────────────────────────────────────────────── # File status segment (index + working) # ────────────────────────────────────────────────────────────────────────────── function Format-IndexStatus { param($Index, $Settings) $parts = [System.Collections.Generic.List[string]]::new() $s = $Settings.Symbols if ($Index.Added -gt 0) { $parts.Add("$($s.Staged)$($Index.Added)") } if ($Index.Modified -gt 0) { $parts.Add("$($s.Staged)~$($Index.Modified)") } if ($Index.Deleted -gt 0) { $parts.Add("$($s.Staged)-$($Index.Deleted)") } if ($Index.Renamed -gt 0) { $parts.Add("$($s.Staged)R$($Index.Renamed)") } if ($Index.Copied -gt 0) { $parts.Add("$($s.Staged)C$($Index.Copied)") } return $parts -join ' ' } function Format-WorkingStatus { param($Working, $Settings) $parts = [System.Collections.Generic.List[string]]::new() $s = $Settings.Symbols if ($Working.Conflicted -gt 0) { $parts.Add("$($s.Conflict)$($Working.Conflicted)") } if ($Working.Modified -gt 0) { $parts.Add("$($s.Unstaged)$($Working.Modified)") } if ($Working.Deleted -gt 0) { $parts.Add("$($s.Unstaged)-$($Working.Deleted)") } if ($Working.Untracked -gt 0) { $parts.Add("$($s.Untracked)$($Working.Untracked)") } return $parts -join ' ' } # ────────────────────────────────────────────────────────────────────────────── # Repo state (MERGE, REBASE n/m, etc.) # ────────────────────────────────────────────────────────────────────────────── function Format-RepoState { param($RepoState, $Settings) if (-not $RepoState.State) { return $null } $label = switch ($RepoState.State) { 'MERGE' { $Settings.Symbols.Merging } 'CHERRY-PICK' { $Settings.Symbols.CherryPicking } 'REVERT' { $Settings.Symbols.Reverting } 'BISECT' { $Settings.Symbols.Bisecting } 'AM' { 'AM' } 'REBASE' { $Settings.Symbols.Rebasing } 'REBASE-i' { $Settings.Symbols.Rebasing } default { $RepoState.State } } if ($RepoState.RebaseTotal -gt 0) { return "$label $($RepoState.RebaseStep)/$($RepoState.RebaseTotal)" } return $label } # ────────────────────────────────────────────────────────────────────────────── # Core prompt builder - ANSI mode # ────────────────────────────────────────────────────────────────────────────── function Build-AnsiPrompt { param($Status, $Settings) $sb = [System.Text.StringBuilder]::new(128) $s = $Settings.Symbols $rst = $Settings.ResetColor.Ansi # Opening delimiter Write-AnsiColor -Builder $sb -Color $Settings.DelimiterColor -Text $Settings.BeforeText # Branch name + upstream color $branchText = Format-BranchName -Branch $Status.Branch -Settings $Settings $branchColor = Get-BranchStatusColor -Branch $Status.Branch -Settings $Settings Write-AnsiColor -Builder $sb -Color $branchColor -Text $branchText # Upstream tracking symbol $trackSymbol = Get-BranchStatusSymbol -Branch $Status.Branch -Settings $Settings if ($trackSymbol) { $null = $sb.Append(' ') Write-AnsiColor -Builder $sb -Color $branchColor -Text $trackSymbol } # Repo state (MERGE, REBASE n/m, etc.) $stateText = Format-RepoState -RepoState $Status.RepoState -Settings $Settings if ($stateText) { $null = $sb.Append(' ') Write-AnsiColor -Builder $sb -Color $Settings.StateColor -Text $stateText } # Index status $indexText = Format-IndexStatus -Index $Status.Index -Settings $Settings if ($indexText) { $null = $sb.Append(' ') Write-AnsiColor -Builder $sb -Color $Settings.StagedColor -Text $indexText } # Separator between index and working if ($indexText -and ($Status.Working.Modified -gt 0 -or $Status.Working.Deleted -gt 0 -or $Status.Working.Untracked -gt 0 -or $Status.Working.Conflicted -gt 0)) { Write-AnsiColor -Builder $sb -Color $Settings.DelimiterColor -Text ' |' } # Working dir status $workText = Format-WorkingStatus -Working $Status.Working -Settings $Settings if ($workText) { $null = $sb.Append(' ') $workColor = if ($Status.Working.Conflicted -gt 0) { $Settings.ConflictColor } else { $Settings.UnstagedColor } Write-AnsiColor -Builder $sb -Color $workColor -Text $workText } # Stash count if ($Settings.ShowStashCount -and $Status.StashCount -gt 0) { Write-AnsiColor -Builder $sb -Color $Settings.StashColor -Text " $($s.Stash)$($Status.StashCount)" } # Closing delimiter + reset Write-AnsiColor -Builder $sb -Color $Settings.DelimiterColor -Text $Settings.AfterText $null = $sb.Append($rst) return $sb.ToString() } # ────────────────────────────────────────────────────────────────────────────── # Core prompt builder - Legacy (Write-Host) mode # ────────────────────────────────────────────────────────────────────────────── function Write-LegacyPrompt { param($Status, $Settings) $s = $Settings.Symbols Write-LegacyColor -Color $Settings.DelimiterColor -Text $Settings.BeforeText $branchText = Format-BranchName -Branch $Status.Branch -Settings $Settings $branchColor = Get-BranchStatusColor -Branch $Status.Branch -Settings $Settings Write-LegacyColor -Color $branchColor -Text $branchText $trackSymbol = Get-BranchStatusSymbol -Branch $Status.Branch -Settings $Settings if ($trackSymbol) { Write-Host -NoNewline ' ' Write-LegacyColor -Color $branchColor -Text $trackSymbol } $stateText = Format-RepoState -RepoState $Status.RepoState -Settings $Settings if ($stateText) { Write-Host -NoNewline ' ' Write-LegacyColor -Color $Settings.StateColor -Text $stateText } $indexText = Format-IndexStatus -Index $Status.Index -Settings $Settings if ($indexText) { Write-Host -NoNewline ' ' Write-LegacyColor -Color $Settings.StagedColor -Text $indexText } if ($indexText -and ($Status.Working.Modified -gt 0 -or $Status.Working.Deleted -gt 0 -or $Status.Working.Untracked -gt 0 -or $Status.Working.Conflicted -gt 0)) { Write-LegacyColor -Color $Settings.DelimiterColor -Text ' |' } $workText = Format-WorkingStatus -Working $Status.Working -Settings $Settings if ($workText) { Write-Host -NoNewline ' ' $workColor = if ($Status.Working.Conflicted -gt 0) { $Settings.ConflictColor } else { $Settings.UnstagedColor } Write-LegacyColor -Color $workColor -Text $workText } if ($Settings.ShowStashCount -and $Status.StashCount -gt 0) { Write-LegacyColor -Color $Settings.StashColor -Text " $($s.Stash)$($Status.StashCount)" } Write-LegacyColor -Color $Settings.DelimiterColor -Text $Settings.AfterText } # ────────────────────────────────────────────────────────────────────────────── # Public API # ────────────────────────────────────────────────────────────────────────────── # Returns a formatted string (ANSI mode) or writes directly (Legacy mode). # Call this inside your prompt function. function Write-GitPrompt { [CmdletBinding()] param( $Status, $Settings = $global:PowerGitSettings ) if (-not $Settings) { return } if (-not $Status -or -not $Status.IsGitRepo) { return } # Compare as string - avoids cross-file enum type lookup issues on PS 5.1 if ($Settings.WriterMode -ne 'Legacy') { return (Build-AnsiPrompt -Status $Status -Settings $Settings) } else { Write-LegacyPrompt -Status $Status -Settings $Settings return '' } } # Convenience: fetch status and render in one call function Get-GitPromptString { [CmdletBinding()] param( $Settings = $global:PowerGitSettings ) if (-not $Settings -or -not $Settings.EnablePrompt) { return '' } try { $status = Get-GitStatus if (-not $status) { return '' } return (Write-GitPrompt -Status $status -Settings $Settings) } catch { # Never crash the prompt return '' } } # ────────────────────────────────────────────────────────────────────────────── # Window title integration # ────────────────────────────────────────────────────────────────────────────── function Set-WindowTitle { [CmdletBinding()] param( $Status, [string]$BasePath = (Get-Location).Path ) if ($Status -and $Status.IsGitRepo) { $repoName = Split-Path $Status.WorkingDir -Leaf $branch = $Status.Branch.Name $host.UI.RawUI.WindowTitle = "$BasePath [$repoName/$branch]" } else { $host.UI.RawUI.WindowTitle = $BasePath } } |