Published in Articles on by Michiel van Oosterhout ~ 4 min read

This is the third article in a series of articles that aims to ultimately reproduce the custom Bash prompt in Windows Command Processor. After creating the Color extension, and making sure we can reliably output Powerline symbols, we are now ready to recreate the Prompt extension to print colorful prompt mockups in Windows Command Processor.

Segmented prompt

We will be using the Powerline symbols found in the Cascadia Mono PL font we installed earlier to separate segments and sub-segments. We'll emit the Unicode code points into a script using UTF-8 encoding, and we'll use the Color extension to achieve the desired effect of a segmented prompt.

The Prompt extension for Windows Command Processor

The Windows PowerShell script below creates the Prompt extension. This extension simplifies the printing of prompt mockups.

# Ensure the directory exists
$path = "$Env:LOCALAPPDATA\Commando\Prompt"
$_ = New-Item $path -Force -ItemType Directory

# Declare the dividers
$E0B0 = [char]0xE0B0
$E0B1 = [char]0xE0B1

# Declare the script contents
$script = @"
@echo off
setlocal enabledelayedexpansion

rem The prompt string
set output=

rem A string holding a segment separator
set sep=

rem Initially the 'previous' background color is the terminal background color
set prev_bg=-

rem Initially there was no previous segment and thus no previous sub-segment
set prev_segment=false
set prev_sub_segment=false

rem This acts as if the previous arg was a separator (e.g '/')
set start_segment=true

rem Counts sub-segments
set count=0

rem Loop over all parameters
:continue
set p=%~1

if "%p%"=="" (
    goto :done
)

rem Handle separator
if "%p%"=="/" (

    rem This signals the next iteration to interpret %p% as colors
    set start_segment=true

    rem Restart counting sub-segments
    set count=0

    rem Continue with next parameter
    shift && goto :continue

)

rem Handle colors
if "%start_segment%"=="true" (

    rem Interpret %p% as colors
    set bg=%p:~0,1%
    set fg=%p:~1,1%

    if "%prev_segment%"=="true" (

        rem Prepare a 'connecting' separator to be rendered later
        for /f %%s in ('call %CMD%\Color\color.cmd !bg!%prev_bg%') do set "sep=%%s"
        set "sep=!sep!$E0B0"
        for /f %%s in ('call %CMD%\Color\color.cmd !bg!!fg!') do set "sep=!sep!%%s"

    )

    rem Prepare for rendering sub-segments
    for /f %%s in ('call %CMD%\Color\color.cmd !bg!!fg!') do set output=%output%%%s

    rem Ensure the next iteration does not interpret %p% as colors
    set start_segment=false

    rem Ensure the next iteration does not add a sub-separator
    set prev_sub_segment=false

    rem Continue with next parameter
    shift && goto :continue

)

rem Counting sub-segments
set /a count=%count% + 1

rem Handle sub-segments
call set evaluated=%p%
if not "%evaluated%"=="" (

    if not "%sep%"=="" (

        rem Render the separator
        set "output=%output%%sep%"

        rem Reset the separator until the next segment
        set sep=

    )

    rem Ensure next separator will 'connect' to this segment
    set prev_bg=%bg%

    if "%prev_sub_segment%"=="true" (

        rem Render a sub-separator
        for /f %%s in ('call %CMD%\Color\color.cmd %bg%-') do set "output=%output%%%s"
        set "output=!output!$E0B1"
        for /f %%s in ('call %CMD%\Color\color.cmd %bg%%fg%') do set "output=!output!%%s"

    )

    rem Ensure the next iteration adds a sub-separator
    set prev_sub_segment=true

    rem Append the sub-segment with 1 space of padding
    set "output=!output! %p% "

    rem Ensure a separator is added eventually
    set prev_segment=true

)

rem Continue with next parameter
shift && goto :continue

:done

if "%prev_segment%"=="true" (

    rem Append the final separator
    for /f %%s in ('call %CMD%\Color\color.cmd 0%prev_bg%') do set output=%output%%%s
    set output=!output!$E0B0
    for /f %%s in ('call %CMD%\Color\color.cmd 00') do set output=!output!%%s

)

rem Echo the prompt without a newline
<NUL set /p _=%output%

endlocal
"@

# Create the Windows Command Processor script
$path = "$path\prompt.cmd"
Set-Content -Encoding UTF8 -Path $path -Value $script

# Ensure proper line endings
((Get-Content $path) -join "`r`n") + "`r`n" | Set-Content -Encoding UTF8 -NoNewline $path

# Remove the BOM
$bytes = Get-Content -Encoding Byte -Path $path
[IO.File]::WriteAllBytes($path, $bytes[3..($bytes.Length - 1)])

Now we can call the function, e.g. call %CMD%\UseCp\usecp.cmd 65001 call %CMD%\Prompt\prompt.cmd bW %USERNAME% %COMPUTERNAME% / mW %CD%, to output a colored prompt mockup with segments and sub-segments. We use the UseCp extension to ensure the code page is set to 65001 (Unicode UTF-8) for the duration of the prompt.cmd script.

Mockups of segmented prompts in Windows Command Processor
Mockups of segmented prompts in Windows Command Processor

As we can see from the screenshot above, for the prompt string bY No %EMPTY% sub-segment / RW %NO% %SEGMENT% / mW %CD%, empty (sub-)segments are not rendered.

Conclusion

The ability to quickly create a mockup of a segmented prompt is great for prototyping a custom prompt. The extension presented in this article simplifies the command for creating such a mockup, requiring only two letters to specify the colors, a space to create sub-segments, and a / to create a new segment.

The next article will show how we can use the Prompt extension to configure a custom Bash Windows Command Processor prompt.

Updates

  • Fixed prompt.cmd to handle parameters that contain a reference to a variable that is not set.