Skip to content

Instantly share code, notes, and snippets.

@guinetik
Last active September 24, 2025 22:54
Show Gist options
  • Select an option

  • Save guinetik/6345c05cdf2c3d1b3c639825ab14cd10 to your computer and use it in GitHub Desktop.

Select an option

Save guinetik/6345c05cdf2c3d1b3c639825ab14cd10 to your computer and use it in GitHub Desktop.
# ============================================================================
# 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