Set default cmd charset to UTF-8 on Windows
1. Issue Description
Recently, I started learning Bubbletea. When I reimplemented its example: Composable views, I encountered a problem, the code did not work as expected.
Expected Behavior
Actual Behavior
2. What led to this?
In the example, it uses lipgloss to render the content displayed in the terminal (code: composable-views: Line46):
// set render style
modelStyle = lipgloss.NewStyle().
Width(15).
Height(5).
Align(lipgloss.Center, lipgloss.Center).
BorderStyle(lipgloss.HiddenBorder())
...
// render content
s += lipgloss.JoinHorizontal(lipgloss.Top, focusedModelStyle.Render(fmt.Sprintf("%4s", m.timer.View())), modelStyle.Render(m.spinner.View()))
After debugging, I discovered that the issue was due to the character set of my terminal being set to 936 (GBK). For Windows code page table see wiki.
In style.Render(), it uses function borders.renderHorizontalEdge to render horizon border:
// github.com/charmbracelet/lipgloss/blob/master/style.go
// Render applies the defined style formatting to a given string.
func (s Style) Render(strs ...string) string {
...
if !inline {
str = s.applyBorder(str)
str = s.applyMargins(str, inline)
}
...
}
func (s Style) applyBorder(str string) string {
...
// Render top
if hasTop {
top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
top = s.styleBorder(top, topFG, topBG)
out.WriteString(top)
out.WriteRune('\n')
}
...
}
// github.com/charmbracelet/lipgloss/blob/master/borders.go
// Render the horizontal (top or bottom) portion of a border.
func renderHorizontalEdge(left, middle, right string, width int) string {
...
leftWidth := ansi.PrintableRuneWidth(left)
rightWidth := ansi.PrintableRuneWidth(right)
runes := []rune(middle)
j := 0
out := strings.Builder{}
out.WriteString(left)
for i := leftWidth + rightWidth; i < width+rightWidth; {
out.WriteRune(runes[j])
j++
if j >= len(runes) {
j = 0
}
i += ansi.PrintableRuneWidth(string(runes[j]))
}
out.WriteString(right)
return out.String()
}
As code above, ansi.PrintableRuneWidth() is used to get the width of a rune.
The length of ─
in GBK is 2, so the function borders.renderHorizontalEdge will print the wrong number of ─
(horizon border).
// github.com/muesli/reflow/blob/master/ansi/buffer.go
// PrintableRuneWidth returns the cell width of the given string.
func PrintableRuneWidth(s string) int {
var n int
var ansi bool
for _, c := range s {
if c == Marker {
// ANSI escape sequence
ansi = true
} else if ansi {
if IsTerminator(c) {
// ANSI sequence terminated
ansi = false
}
} else {
n += runewidth.RuneWidth(c)
}
}
return n
}
// github.com/mattn/go-runewidth/blob/master/runewidth.go
// RuneWidth returns the number of cells in r.
// See http://www.unicode.org/reports/tr11/
func RuneWidth(r rune) int {
return DefaultCondition.RuneWidth(r)
}
// RuneWidth returns the number of cells in r.
// See http://www.unicode.org/reports/tr11/
func (c *Condition) RuneWidth(r rune) int {
if r < 0 || r > 0x10FFFF {
return 0
}
if len(c.combinedLut) > 0 {
return int(c.combinedLut[r>>1]>>(uint(r&1)*4)) & 3
}
// optimized version, verified by TestRuneWidthChecksums()
if !c.EastAsianWidth {
switch {
case r < 0x20:
return 0
case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint
return 0
case r < 0x300:
return 1
case inTable(r, narrow):
return 1
case inTables(r, nonprint, combining):
return 0
case inTable(r, doublewidth):
return 2
default:
return 1
}
} else {
switch {
case inTables(r, nonprint, combining):
return 0
case inTable(r, narrow):
return 1
case inTables(r, ambiguous, doublewidth):
return 2
case !c.StrictEmojiNeutral && inTables(r, ambiguous, emoji, narrow):
return 2
default:
return 1
}
}
}
|
in GBK will match inTble(r, narrow)
in the else block.
The table narrow
is generated by generate according to https://home.unicode.org/.
3. Demo to explain how it happened
package main
import (
"fmt"
"github.com/charmbracelet/lipgloss"
)
func main() {
var style = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("63"))
fmt.Print(style.Render("hello"))
}
// output
┌───┐
│hello│
└───┘
Top border render steps:
- Get the horizon length: 7 = 5 + 2 =
len(“hello”)
+ number of vertical border|
- Get the middle horizon borders
─
(its length is 2 in GBK): only 2─
(7-2 % 2), expect 5 (7-2 % 1)
4. How to solve
cmd
Get code page of Use chcp
to get the current code page of cmd
:
> chcp
936
Change code page to UTF-8 Temporarily
> chcp 65001
cmd
code page
Set default - Start -> Run -> regedit
- Go to
[HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\Autorun]
- Change the value to
@chcp 65001>nul
If Autorun
is not present, you can add a New String
.
This approach will auto-execute @chcp 65001>nul
when cmd
starts.
Reference
- https://superuser.com/questions/269818/change-default-code-page-of-windows-console-to-utf-8/269857#269857
- https://en.wikipedia.org/wiki/Windows_code_page
- https://github.com/charmbracelet/bubbletea/blob/master/examples/composable-views/main.go#L46
- https://home.unicode.org/
- https://github.com/charmbracelet/lipgloss
- https://github.com/mattn/go-runewidth/
- https://github.com/muesli/reflow