Last active
September 24, 2025 22:54
-
-
Save guinetik/6345c05cdf2c3d1b3c639825ab14cd10 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # Modular PowerShell Prompt | |
| # Author: Guinetik | |
| # Description: A modern, modular PowerShell prompt with powerline separators, | |
| # Git integration, language detection, and system monitoring | |
| # Features: Git status, Java/Node.js detection, RAM usage, right-aligned time | |
| # ============================================================================ | |
| # Global color and icon setup | |
| $esc = [char]27 | |
| $reset = "$esc[0m" | |
| # Modern Color Palette - Red to Grey Gradient | |
| $colors = @{ | |
| # Background colors - Red to grey gradient for visual hierarchy | |
| 'bg1' = "$esc[48;2;220;53;69m" # Primary red (#dc3545) | |
| 'bg2' = "$esc[48;2;178;34;52m" # Darker red (#b22234) | |
| 'bg3' = "$esc[48;2;128;128;128m" # Medium grey (#808080) | |
| 'bg4' = "$esc[48;2;96;96;96m" # Dark grey (#606060) | |
| 'bg5' = "$esc[48;2;64;64;64m" # Darker grey (#404040) | |
| # Foreground colors for optimal readability | |
| 'fg_dark' = "$esc[38;2;0;0;0m" # Black text | |
| 'fg_light' = "$esc[38;2;255;255;255m" # White text | |
| 'fg_medium' = "$esc[38;2;220;220;220m" # Light grey text | |
| # Separator colors (foreground colors matching backgrounds for smooth transitions) | |
| 'sep1' = "$esc[38;2;220;53;69m" # Primary red | |
| 'sep2' = "$esc[38;2;178;34;52m" # Darker red | |
| 'sep3' = "$esc[38;2;128;128;128m" # Medium grey | |
| 'sep4' = "$esc[38;2;96;96;96m" # Dark grey | |
| 'sep5' = "$esc[38;2;64;64;64m" # Darker grey | |
| } | |
| # Powerline separators using Nerd Font Unicode codepoints | |
| # Requires a Nerd Font (FiraCode Nerd Font, JetBrains Mono Nerd Font, etc.) | |
| $separators = @{ | |
| 'right_arrow' = [char]0xe0b0 # Powerline right solid arrow → | |
| 'left_arrow' = [char]0xe0b2 # Powerline left solid arrow ← | |
| 'right_thin' = [char]0xe0b1 # Powerline right thin arrow (alternative) | |
| 'left_thin' = [char]0xe0b3 # Powerline left thin arrow (alternative) | |
| } | |
| # Nerd Font icons for visual enhancement | |
| # All icons use Unicode codepoints from the Nerd Fonts icon set | |
| $icons = @{ | |
| 'folder' = "" # Folder icon | |
| 'home' = "" # Home directory icon | |
| 'git' = "" # Git branch icon | |
| 'nodejs' = "" # Node.js icon | |
| 'java' = "" # Java icon | |
| 'time' = "" # Clock icon | |
| 'ram' = "" # RAM/Memory icon | |
| 'user' = "" # User icon | |
| 'admin' = "" # Administrator/elevated icon | |
| 'modified' = "" # Git modified files icon | |
| 'added' = "" # Git added files icon | |
| 'deleted' = "" # Git deleted files icon | |
| 'untracked' = "" # Git untracked files icon | |
| 'ahead' = "" # Git ahead commits icon | |
| 'behind' = "" # Git behind commits icon | |
| 'clean' = "" # Git clean repository icon | |
| 'warning' = "" # Warning icon for errors | |
| } | |
| # ============================================================================ | |
| # SEGMENT FUNCTIONS | |
| # Each function returns a formatted string for a specific prompt segment | |
| # Returns empty string if the segment shouldn't be displayed | |
| # ============================================================================ | |
| <# | |
| .SYNOPSIS | |
| Creates the decorative start segment of the prompt | |
| .DESCRIPTION | |
| Returns a red background segment with decorative characters ░▒▓ | |
| Provides visual flair and marks the beginning of the prompt line | |
| .RETURNS | |
| String with red background and decorative characters | |
| #> | |
| function Get-StartSegment { | |
| return "$($colors.bg1)$($colors.fg_light)░▒▓$reset" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the user information segment | |
| .DESCRIPTION | |
| Displays current username with appropriate icon | |
| Shows admin icon if running with elevated privileges | |
| Uses red background to match the start segment | |
| .RETURNS | |
| String with username and admin/user icon on red background | |
| #> | |
| function Get-UserSegment { | |
| $user = [Environment]::UserName | |
| $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| $icon = if ($isAdmin) { $icons.admin } else { $icons.user } | |
| return "$($colors.bg1)$($colors.fg_light) $icon $user $reset" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the current directory segment | |
| .DESCRIPTION | |
| Shows the current working directory with smart path substitutions | |
| Replaces common directories (Documents, Downloads, etc.) with icons | |
| Truncates very long paths for better readability | |
| Uses darker red background for visual hierarchy | |
| .RETURNS | |
| String with current directory path on darker red background | |
| #> | |
| function Get-DirectorySegment { | |
| $currentPath = Get-Location | |
| $path = $currentPath.Path | |
| # Path substitutions for common directories with icons | |
| $substitutions = @{ | |
| "$env:USERPROFILE\Documents" = " Documents" | |
| "$env:USERPROFILE\Downloads" = " Downloads" | |
| "$env:USERPROFILE\Music" = " Music" | |
| "$env:USERPROFILE\Pictures" = " Pictures" | |
| "$env:USERPROFILE" = "$($icons.home) ~" | |
| } | |
| # Apply path substitutions | |
| foreach ($sub in $substitutions.GetEnumerator()) { | |
| if ($path.StartsWith($sub.Key)) { | |
| $path = $path -replace [regex]::Escape($sub.Key), $sub.Value | |
| break | |
| } | |
| } | |
| # Truncate long paths to prevent prompt overflow | |
| if ($path.Length -gt 50) { | |
| $pathParts = $path.Split([System.IO.Path]::DirectorySeparatorChar) | |
| if ($pathParts.Length -gt 3) { | |
| $path = "$($pathParts[0])\…\$($pathParts[-2])\$($pathParts[-1])" | |
| } | |
| } | |
| return "$($colors.bg2)$($colors.fg_light) $($icons.folder) $path $reset" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the Git branch segment | |
| .DESCRIPTION | |
| Shows the current Git branch name if in a Git repository | |
| Only appears when inside a Git repository with a valid branch | |
| Uses grey background to distinguish from path information | |
| .RETURNS | |
| String with Git branch name or empty string if not in Git repo | |
| #> | |
| function Get-GitBranchSegment { | |
| if (-not (Get-Command git -ErrorAction SilentlyContinue)) { | |
| return "" | |
| } | |
| try { | |
| $branch = git rev-parse --abbrev-ref HEAD 2>$null | |
| if (-not $branch -or $branch -eq 'HEAD') { | |
| return "" | |
| } | |
| return "$($colors.bg3)$($colors.fg_light) $($icons.git) $branch $reset" | |
| } | |
| catch { | |
| return "" | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the Git status segment | |
| .DESCRIPTION | |
| Shows detailed Git repository status including: | |
| - Modified, added, deleted, and untracked file counts | |
| - Ahead/behind commit indicators relative to upstream | |
| - Clean repository indicator when no changes present | |
| Only appears when in a Git repository | |
| .RETURNS | |
| String with Git status information or empty string if not applicable | |
| #> | |
| function Get-GitStatusSegment { | |
| if (-not (Get-Command git -ErrorAction SilentlyContinue)) { | |
| return "" | |
| } | |
| try { | |
| $branch = git rev-parse --abbrev-ref HEAD 2>$null | |
| if (-not $branch -or $branch -eq 'HEAD') { | |
| return "" | |
| } | |
| $status = git status --porcelain 2>$null | |
| $symbols = @() | |
| if (-not $status) { | |
| # Clean repository - no changes | |
| $symbols += $icons.clean | |
| } else { | |
| # Parse git status output for different change types | |
| $modified = ($status | Where-Object { $_ -match '^.M' }).Count | |
| $added = ($status | Where-Object { $_ -match '^A.' }).Count | |
| $deleted = ($status | Where-Object { $_ -match '^.D' }).Count | |
| $untracked = ($status | Where-Object { $_ -match '^\?\?' }).Count | |
| # Add symbols with counts (space between icon and number for readability) | |
| if ($modified -gt 0) { $symbols += "$($icons.modified) $modified" } | |
| if ($added -gt 0) { $symbols += "$($icons.added) $added" } | |
| if ($deleted -gt 0) { $symbols += "$($icons.deleted) $deleted" } | |
| if ($untracked -gt 0) { $symbols += "$($icons.untracked) $untracked" } | |
| } | |
| # Check ahead/behind status relative to upstream branch | |
| try { | |
| $aheadBehindOutput = git rev-list --left-right --count HEAD...@{upstream} 2>$null | |
| if ($aheadBehindOutput) { | |
| $counts = $aheadBehindOutput -split "`t" | |
| if ($counts.Length -eq 2) { | |
| $behind = [int]$counts[0] | |
| $ahead = [int]$counts[1] | |
| if ($ahead -gt 0) { $symbols += "$($icons.ahead) $ahead" } | |
| if ($behind -gt 0) { $symbols += "$($icons.behind) $behind" } | |
| } | |
| } | |
| } | |
| catch { | |
| # Ignore ahead/behind errors (no upstream, etc.) | |
| } | |
| if ($symbols.Count -eq 0) { | |
| return "" | |
| } | |
| return "$($colors.bg3)$($colors.fg_light) $($symbols -join ' ') $reset" | |
| } | |
| catch { | |
| return "" | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the Node.js project segment | |
| .DESCRIPTION | |
| Detects Node.js projects by presence of package.json file | |
| Shows Node.js version if executable is found | |
| Shows warning if project detected but Node.js not installed | |
| Provides graceful error handling to prevent prompt failures | |
| .RETURNS | |
| String with Node.js version/status or empty string if not Node.js project | |
| #> | |
| function Get-NodeJsSegment { | |
| if (-not (Test-Path "package.json")) { | |
| return "" | |
| } | |
| if (-not (Get-Command node -ErrorAction SilentlyContinue)) { | |
| # Node.js project detected but executable not found | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.warning) Node not found $reset" | |
| } | |
| try { | |
| $version = (node --version 2>$null) -replace "v", "" | |
| if ($version) { | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.nodejs) Node $version $reset" | |
| } else { | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.warning) Node error $reset" | |
| } | |
| } | |
| catch { | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.warning) Node error $reset" | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the Java project segment | |
| .DESCRIPTION | |
| Detects Java projects by presence of pom.xml file (Maven projects) | |
| Shows Java version if executable is found | |
| Shows warning if project detected but Java not installed | |
| Handles both old (-version) and new (--version) Java version syntax | |
| Provides graceful error handling to prevent prompt failures | |
| .RETURNS | |
| String with Java version/status or empty string if not Java project | |
| #> | |
| function Get-JavaSegment { | |
| if (-not (Test-Path "pom.xml")) { | |
| return "" | |
| } | |
| if (-not (Get-Command java -ErrorAction SilentlyContinue)) { | |
| # Java project detected but executable not found | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.warning) Java not found $reset" | |
| } | |
| try { | |
| $javaOutput = java --version 2>$null | |
| if (-not $javaOutput) { | |
| # Try older Java version syntax for compatibility | |
| $javaOutput = java -version 2>&1 | |
| } | |
| if ($javaOutput) { | |
| # Extract version number from Java output (handles various formats) | |
| $versionLine = $javaOutput | Select-Object -First 1 | |
| if ($versionLine -match '(\d+\.?\d*\.?\d*)') { | |
| $version = $matches[1] | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.java) Java $version $reset" | |
| } | |
| } | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.warning) Java error $reset" | |
| } | |
| catch { | |
| return "$($colors.bg4)$($colors.fg_light) $($icons.warning) Java error $reset" | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Placeholder functions for removed language segments | |
| .DESCRIPTION | |
| These functions are kept as stubs to maintain compatibility | |
| Return empty strings so they don't affect the prompt | |
| .RETURNS | |
| Empty string | |
| #> | |
| function Get-PythonSegment { | |
| return "" | |
| } | |
| function Get-DockerSegment { | |
| return "" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the RAM usage segment | |
| .DESCRIPTION | |
| Shows available/total RAM in GB format with color coding | |
| Colors indicate memory pressure: | |
| - Green: Low usage (<60%) | |
| - Yellow: Medium usage (60-80%) | |
| - Red: High usage (>80%) | |
| Appears on the right side of the prompt | |
| .RETURNS | |
| String with RAM usage information or empty string on error | |
| #> | |
| function Get-RamSegment { | |
| try { | |
| $os = Get-CimInstance -ClassName Win32_OperatingSystem | |
| if ($os) { | |
| $totalMemory = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1) | |
| $freeMemory = [math]::Round($os.FreePhysicalMemory / 1MB, 1) | |
| $usedMemory = $totalMemory - $freeMemory | |
| $usedPercent = [math]::Round(($usedMemory / $totalMemory) * 100) | |
| # Color coding based on memory usage percentage | |
| $availableColor = if ($usedPercent -gt 80) { | |
| "$esc[38;2;255;200;200m" # Light red for high usage | |
| } elseif ($usedPercent -gt 60) { | |
| "$esc[38;2;255;255;200m" # Light yellow for medium usage | |
| } else { | |
| "$esc[38;2;200;255;200m" # Light green for low usage | |
| } | |
| # Total memory in subdued light grey | |
| $totalColor = "$esc[38;2;180;180;180m" | |
| return "$($colors.bg5)$($colors.fg_light) $($icons.ram) $availableColor${freeMemory}GB$totalColor/${totalMemory}GB$reset" | |
| } | |
| } | |
| catch { | |
| return "" | |
| } | |
| return "" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates the time segment | |
| .DESCRIPTION | |
| Shows current time in HH:mm format | |
| Uses red background to match the start segment for visual balance | |
| Appears on the right side of the prompt line | |
| .RETURNS | |
| String with current time on red background | |
| #> | |
| function Get-TimeSegment { | |
| $time = Get-Date -Format "HH:mm" | |
| return "$($colors.bg1)$($colors.fg_light) $($icons.time) $time $reset" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates powerline separator between segments | |
| .DESCRIPTION | |
| Generates the appropriate powerline arrow separator between two segments | |
| Handles color transitions for smooth visual flow | |
| Returns final separator if no target segment specified | |
| .PARAMETER fromSegment | |
| The segment type we're transitioning from | |
| .PARAMETER toSegment | |
| The segment type we're transitioning to (null for final separator) | |
| .RETURNS | |
| String with powerline separator and appropriate colors | |
| #> | |
| function Get-PromptSeparator($fromSegment, $toSegment) { | |
| $separatorMap = @{ | |
| 'start' = $colors.sep1 | |
| 'user' = $colors.sep1 | |
| 'directory' = $colors.sep2 | |
| 'git' = $colors.sep3 | |
| 'language' = $colors.sep4 | |
| 'time' = $colors.sep5 | |
| } | |
| $fromColor = $separatorMap[$fromSegment] | |
| if ($toSegment) { | |
| $toBackground = switch ($toSegment) { | |
| 'user' { $colors.bg1 } | |
| 'directory' { $colors.bg2 } | |
| 'git' { $colors.bg3 } | |
| 'language' { $colors.bg4 } | |
| 'time' { $colors.bg5 } | |
| } | |
| return "$toBackground$fromColor$($separators.right_arrow)$reset" | |
| } else { | |
| # Final separator - no background | |
| return "$fromColor$($separators.right_arrow)$reset" | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Calculates the visible length of text with ANSI escape sequences | |
| .DESCRIPTION | |
| Strips ANSI escape sequences to calculate the actual visible character count | |
| Used for proper terminal width calculations and right-alignment | |
| .PARAMETER text | |
| Text string that may contain ANSI escape sequences | |
| .RETURNS | |
| Integer representing the visible character count | |
| #> | |
| function Get-VisibleLength($text) { | |
| return ($text -replace "$esc\[[0-9;]*m", "").Length | |
| } | |
| # ============================================================================ | |
| # MAIN PROMPT FUNCTION | |
| # Orchestrates all segments and creates the final prompt line | |
| # ============================================================================ | |
| <# | |
| .SYNOPSIS | |
| Main PowerShell prompt function | |
| .DESCRIPTION | |
| Builds the complete prompt by: | |
| 1. Assembling left-side segments (user, directory, git, languages) | |
| 2. Adding powerline separators between segments | |
| 3. Creating right-side segments (RAM, time) with proper alignment | |
| 4. Calculating terminal width for proper spacing | |
| 5. Outputting the formatted prompt line | |
| The prompt structure: | |
| [decorative][user][directory][git branch][git status][languages] ... [RAM][time][decorative] | |
| .RETURNS | |
| String "❯ " as the actual prompt character for user input | |
| #> | |
| function prompt { | |
| $leftSegments = @() | |
| # Build left side segments with powerline separators | |
| $leftSegments += Get-StartSegment | |
| $leftSegments += Get-UserSegment | |
| $leftSegments += "$($colors.bg2)$($colors.sep1)$($separators.right_arrow)$reset" | |
| $leftSegments += Get-DirectorySegment | |
| # Git segments (branch and status) | |
| $gitBranch = Get-GitBranchSegment | |
| $gitStatus = Get-GitStatusSegment | |
| if ($gitBranch -or $gitStatus) { | |
| $leftSegments += "$($colors.bg3)$($colors.sep2)$($separators.right_arrow)$reset" | |
| $leftSegments += $gitBranch | |
| $leftSegments += $gitStatus | |
| $lastSegment = 'git' | |
| $lastBgColor = $colors.bg3 | |
| $lastSepColor = $colors.sep3 | |
| } else { | |
| $lastSegment = 'directory' | |
| $lastBgColor = $colors.bg2 | |
| $lastSepColor = $colors.sep2 | |
| } | |
| # Language/framework segments (Java and Node.js) | |
| $nodejs = Get-NodeJsSegment | |
| $java = Get-JavaSegment | |
| if ($nodejs -or $java) { | |
| $leftSegments += "$($colors.bg4)$lastSepColor$($separators.right_arrow)$reset" | |
| $leftSegments += $nodejs | |
| $leftSegments += $java | |
| $lastSepColor = $colors.sep4 | |
| } | |
| # Final separator for left side | |
| $leftSegments += "$lastSepColor$($separators.right_arrow)$reset" | |
| # Build right side segments (right-aligned) | |
| $ramSegment = Get-RamSegment | |
| $timeSegment = Get-TimeSegment | |
| $rightSegments = @() | |
| # Add left arrow separator before RAM segment | |
| $rightSegments += "$($colors.sep5)$($separators.left_arrow)$reset" | |
| $rightSegments += "$($colors.bg5)$($colors.sep5)$($separators.left_arrow)$reset" | |
| if ($ramSegment) { | |
| $rightSegments += $ramSegment | |
| # Add separator between RAM and time (dark grey to red) | |
| $rightSegments += "$($colors.bg1)$($colors.sep5)$($separators.right_arrow)$reset" | |
| } else { | |
| # If no RAM segment, go directly to red background for time | |
| $rightSegments += "$($colors.bg1)$($colors.sep5)$($separators.right_arrow)$reset" | |
| } | |
| $rightSegments += $timeSegment | |
| # Add decorative end matching the start | |
| $rightSegments += "$($colors.bg1)$($colors.fg_light)░▒▓$reset" | |
| # Calculate lengths for proper spacing | |
| $leftText = $leftSegments -join '' | |
| $rightText = $rightSegments -join '' | |
| $leftLength = Get-VisibleLength $leftText | |
| $rightLength = Get-VisibleLength $rightText | |
| # Get terminal width (fallback to 80 if unavailable) | |
| $terminalWidth = try { | |
| $Host.UI.RawUI.BufferSize.Width | |
| } catch { | |
| 80 | |
| } | |
| # Calculate padding to right-align the time segment | |
| $totalContentLength = $leftLength + $rightLength | |
| $padding = $terminalWidth - $totalContentLength | |
| $padding = [Math]::Max(1, $padding) # Ensure at least 1 space | |
| # Output the complete prompt line | |
| Write-Host $leftText -NoNewline | |
| Write-Host (' ' * $padding) -NoNewline | |
| Write-Host $rightText -NoNewline | |
| Write-Host "" | |
| # Return the actual prompt character | |
| return "❯ " | |
| } | |
| # ============================================================================ | |
| # END OF PROMPT CONFIGURATION | |
| # | |
| # Installation Instructions: | |
| # 1. Save this file to your PowerShell profile location | |
| # 2. Run: . $PROFILE to reload | |
| # 3. Install a Nerd Font for proper icon display | |
| # 4. Ensure your terminal supports 24-bit color (most modern terminals do) | |
| # | |
| # Customization: | |
| # - Modify the $colors hashtable to change the color scheme | |
| # - Update the $icons hashtable to change or add icons | |
| # - Add new Get-*Segment functions for additional information | |
| # - Adjust the main prompt function to include new segments | |
| # ============================================================================ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment