Eldritch Computer

Configuring a terminal emulator for a light theme

Some have called me mad, but I prefer dark text on a light background ("light theme") to light text on a dark background ("dark theme"). Terminals almost always use dark themes by default, so this article will describe how to reconfigure a terminal to use a light theme.

I use Windows Terminal (on Windows) to use both Powershell and various command lines on Linux through Windows Subsystem for Linux. This article will cover changing the colours for Powershell as well as for Linux terminals.

Changing the terminal colour palette

Most of the colours in a terminal draw from a palette of 16 colours which is defined by the terminal emulator being used. All common terminal emulators can be configured to use different colours for these colours. How to do this depends on which terminal emulator you are using, so I won't cover all the possibilities here. But as an illustrative example, here's the dialogue in Windows Terminal for changing the colour scheme.

Immediately after changing the terminal colour scheme, you should notice that the colours used by most applications have changed significantly. Most programs draw all their colours from the terminal palette.

Changing the colours used by Powershell

Changing the terminal palette didn't change every colour, so I started looking for ways to change the colours which hadn't changed. I noticed that Powershell's command completion suggestions were almost invisible against my new light background, so I looked for a way to change the colours it uses.

I found this excellent guide which describes how to change Powershell's colours, and also provides a pre-formulated colour palette which works well for light mode. (Thank you to the authors of this article!)

Command completions in Powershell are apparently under the control of PSReadLine, Powershell's command editor. PSReadLine chooses colours from its options, which can be changed using the Set-PSReadLineOption cmdlet. The linked article suggests defining a variable, $ISETheme, whose value is a mapping between syntax elements and colours:

$ISETheme = @{
    Command                  = $PSStyle.Foreground.FromRGB(0x0000FF)
    Comment                  = $PSStyle.Foreground.FromRGB(0x006400)
    ContinuationPrompt       = $PSStyle.Foreground.FromRGB(0x0000FF)
    Default                  = $PSStyle.Foreground.FromRGB(0x0000FF)
    Emphasis                 = $PSStyle.Foreground.FromRGB(0x287BF0)
    Error                    = $PSStyle.Foreground.FromRGB(0xE50000)
    InlinePrediction         = $PSStyle.Foreground.FromRGB(0x93A1A1)
    Keyword                  = $PSStyle.Foreground.FromRGB(0x00008b)
    ListPrediction           = $PSStyle.Foreground.FromRGB(0x06DE00)
    Member                   = $PSStyle.Foreground.FromRGB(0x000000)
    Number                   = $PSStyle.Foreground.FromRGB(0x800080)
    Operator                 = $PSStyle.Foreground.FromRGB(0x757575)
    Parameter                = $PSStyle.Foreground.FromRGB(0x000080)
    String                   = $PSStyle.Foreground.FromRGB(0x8b0000)
    Type                     = $PSStyle.Foreground.FromRGB(0x008080)
    Variable                 = $PSStyle.Foreground.FromRGB(0xff4500)
    ListPredictionSelected   = $PSStyle.Background.FromRGB(0x93A1A1)
    Selection                = $PSStyle.Background.FromRGB(0x00BFFF)
}

Powershell also uses colours not from the terminal palette in output, which must be configured separately from PSReadLine. The article provides another set of example colours which look better with a light background and dark foreground (thank you!).

As the article explains, Powershell uses another different set of colours to colourise the output of commands (from Powershell 7.2 forwards). These colours are defined by the variable $PSStyle. The article suggests setting $PSStyle like this for a light background:

$PSStyle.Formatting.FormatAccent       = "`e[32m"
$PSStyle.Formatting.TableHeader        = "`e[32m"
$PSStyle.Formatting.ErrorAccent        = "`e[36m"
$PSStyle.Formatting.Error              = "`e[31m"
$PSStyle.Formatting.Warning            = "`e[33m"
$PSStyle.Formatting.Verbose            = "`e[33m"
$PSStyle.Formatting.Debug              = "`e[33m"
$PSStyle.Progress.Style                = "`e[33m"
$PSStyle.FileInfo.Directory            = $PSStyle.Background.FromRgb(0x2f6aff) +
                                         $PSStyle.Foreground.BrightWhite
$PSStyle.FileInfo.SymbolicLink         = "`e[36m"
$PSStyle.FileInfo.Executable           = "`e[95m"
$PSStyle.FileInfo.Extension['.ps1']    = "`e[36m"
$PSStyle.FileInfo.Extension['.ps1xml'] = "`e[36m"
$PSStyle.FileInfo.Extension['.psd1']   = "`e[36m"
$PSStyle.FileInfo.Extension['.psm1']   = "`e[36m"

These colour options will not persist once your Powershell session ends, so to keep the options set, the commands you ran to change the colours need to be written to your Powershell profile. The variable $PROFILE stores the location of your profile. You can print the its value at the Powershell prompt like this:

PS C:\Users\adeptangel> $PROFILE
C:\Users\adeptangel\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
PS C:\Users\adeptangel>

I got stuck here for a little while trying to open the profile in Notepad. Passing the value of $PROFILE to be opened in Notepad with notepad.exe $PROFILE didn't work because the directory Documents/Powershell didn't actually exist. Once I realised what was going on, I created the directory and then wrote these lines to the file:

$ISETheme = @{
    Command                  = $PSStyle.Foreground.FromRGB(0x0000FF)
    Comment                  = $PSStyle.Foreground.FromRGB(0x006400)
    ContinuationPrompt       = $PSStyle.Foreground.FromRGB(0x0000FF)
    Default                  = $PSStyle.Foreground.FromRGB(0x0000FF)
    Emphasis                 = $PSStyle.Foreground.FromRGB(0x287BF0)
    Error                    = $PSStyle.Foreground.FromRGB(0xE50000)
    InlinePrediction         = $PSStyle.Foreground.FromRGB(0x93A1A1)
    Keyword                  = $PSStyle.Foreground.FromRGB(0x00008b)
    ListPrediction           = $PSStyle.Foreground.FromRGB(0x06DE00)
    Member                   = $PSStyle.Foreground.FromRGB(0x000000)
    Number                   = $PSStyle.Foreground.FromRGB(0x800080)
    Operator                 = $PSStyle.Foreground.FromRGB(0x757575)
    Parameter                = $PSStyle.Foreground.FromRGB(0x000080)
    String                   = $PSStyle.Foreground.FromRGB(0x8b0000)
    Type                     = $PSStyle.Foreground.FromRGB(0x008080)
    Variable                 = $PSStyle.Foreground.FromRGB(0xff4500)
    ListPredictionSelected   = $PSStyle.Background.FromRGB(0x93A1A1)
    Selection                = $PSStyle.Background.FromRGB(0x00BFFF)
}

$PSStyle.Formatting.FormatAccent       = "`e[32m"
$PSStyle.Formatting.TableHeader        = "`e[32m"
$PSStyle.Formatting.ErrorAccent        = "`e[36m"
$PSStyle.Formatting.Error              = "`e[31m"
$PSStyle.Formatting.Warning            = "`e[33m"
$PSStyle.Formatting.Verbose            = "`e[33m"
$PSStyle.Formatting.Debug              = "`e[33m"
$PSStyle.Progress.Style                = "`e[33m"
$PSStyle.FileInfo.Directory            = $PSStyle.Background.FromRgb(0x2f6aff) +
                                         $PSStyle.Foreground.BrightWhite
$PSStyle.FileInfo.SymbolicLink         = "`e[36m"
$PSStyle.FileInfo.Executable           = "`e[95m"
$PSStyle.FileInfo.Extension['.ps1']    = "`e[36m"
$PSStyle.FileInfo.Extension['.ps1xml'] = "`e[36m"
$PSStyle.FileInfo.Extension['.psd1']   = "`e[36m"
$PSStyle.FileInfo.Extension['.psm1']   = "`e[36m"

Set-PSReadLineOption -Colors $ISETheme

Changing LS_COLORS

GNU ls, which is the version of ls distributed with most Linux distributions, will display different colours for different kinds of file in a terminal (so long as the --color option is not set to none). GNU ls has a built-in mapping of colours to file types, but this can be overriden with the environment variable LS_COLORS.

I was expecting to find the default foreground colours for ls had poor contrast against a light background, but they're actually fine, especially since most of them are chosen from the 16-colour terminal palette. However, there is one particularly problematic colour: the colour for the "other writeable" file type. By default, files of that type will display with light blue text on a light blue background, as depicted here, taken from a Unix & Linux stack exchange question:

The names of kinds of file recognised by GNU ls, coloured according to the default colours for ls.

I find this very difficult to read so I set about trying to change it. I spent some time using the GNU program dircolors, which can be used to generate a value for LS_COLORS from a more verbose file which is easier to understand. But as far as I could tell, the only way to automate the process of generating LS_COLORS from the dircolors file was to use eval to execute some lines of shell script. I'm not confident about what exactly eval does, and I only wanted to make a single minor change to the colours anyway, so eventually I set LS_COLORS manually in my shell runcommands file:

# ls_colors: change the colour of Other Writeable to make it readable
LS_COLORS=$LS_COLORS:"ow=30;106"

This makes those entries much more readable. Yay! Now if only I knew what an "other writeable" actually is.

Changing the colours used by programs on Linux

Most Linux terminal programs use the terminal's colour palette only, which means that they should work properly straight away! I did encounter two issues while using some more complicated programs, however.

The Invisible Cursor Problem

I use the text editor Neovim in the terminal, which can be programmed to use custom colour schemes. Neovim will change the background colour according to the colour scheme, so contrast with the standard terminal background colour is not a problem. But the colour of the cursor (which, in Windows Terminal, is part of the terminal colour palette) won't change. If the normal terminal background colour is dark, the cursor will be light, and the same reversed. So if Neovim changes the background to a dark colour, and the terminal colour scheme background colour is light, the dark cursor will become invisible against the dark background (and the same reversed).

I don't know any solution to this, apart from using a terminal emulator which chooses the colour for the cursor dynamically to avoid this problem. I don't know whether any terminal emulators do this.

The Terminal "White" Colour Problem

I use the terminal IRC client catgirl. By default, catgirl picks colours for IRC nicks from a wide range of colours, including the 16 terminal colours but not limited to them. Before I ran catgirl with a light background, I added the line hash = 0,15 to my configuration file. This limited the colours used for the nicks to the colours from the terminal palette (colours 0 through 15), in case the default colour palette included colours with poor contrast against a light background.

Once I started catgirl, I noticed that system messages, like direct messages from the NickServ bot, were almost entirely unreadable. At first I worried that catgirl had hardcoded a colour which would be illegible with a light background. Then I opened an IRC channel, and saw that, despite limiting the colours to only the terminal palette, some of the nicks were the same unreadable colour as the messages from NickServ. I realised that one of the foreground colours from the terminal palette must have contrast too poor against the background to be visible. Sure enough, the "white" colour was, well, white. This made it unreadable against the background, which was only a different shade of white.

I solved this problem by changing my terminal colour scheme again, to one where the "white" colour is actually dark, which gives it contrast against a light background. The colour scheme I chose is Rosé Pine Light.