Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mode 2027 grapheme clustering stubs #1105

Merged
merged 4 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymanbagabas/go-udiff v0.2.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.2.2 // indirect
github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/charmbracelet/x/windows v0.2.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ
github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.2.2 h1:BC7xzaVpfWIYZRNE8NhO9zo8KA4eGUL6L/JWXDh3GF0=
github.com/charmbracelet/x/ansi v0.2.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/teatest v0.0.0-20240521184646-23081fb03b28 h1:sOWKNRjt8uOEVgPiJVIJCse1+mUDM2F/vYY6W0Go640=
Expand Down
2 changes: 1 addition & 1 deletion examples/simple/testdata/TestApp.golden
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[?25l[?2004hHi. This program will exit in 10 seconds.
[?25l[?2004h[?2027h[?2027$pHi. This program will exit in 10 seconds.

To quit sooner press ctrl-c, or press ctrl-z to suspend...
Hi. This program will exit in 9 seconds.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/charmbracelet/bubbletea
go 1.18

require (
github.com/charmbracelet/x/ansi v0.2.3
github.com/charmbracelet/lipgloss v0.13.0
github.com/charmbracelet/x/ansi v0.2.2
github.com/charmbracelet/x/term v0.2.0
github.com/charmbracelet/x/windows v0.2.0
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.2.2 h1:BC7xzaVpfWIYZRNE8NhO9zo8KA4eGUL6L/JWXDh3GF0=
github.com/charmbracelet/x/ansi v0.2.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw=
Expand Down
15 changes: 15 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,18 @@ func _WithWindowsInputMode() ProgramOption { //nolint:unused
p.win32Input = true
}
}

// WithoutGraphemeClustering disables grapheme clustering. This is useful if you
// want to disable grapheme clustering for your program.
//
// Grapheme clustering is a character width calculation method that accurately
// calculates the width of wide characters in a terminal. This is useful for
// properly rendering double width characters such as emojis and CJK
// characters.
//
// See https://mitchellh.com/writing/grapheme-clusters-in-terminals
func WithoutGraphemeClustering() ProgramOption {
aymanbagabas marked this conversation as resolved.
Show resolved Hide resolved
return func(p *Program) {
p.startupOptions |= withoutGraphemeClustering
}
}
5 changes: 3 additions & 2 deletions renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type repaintMsg struct{}

// Terminal modes used by SetMode and Mode in Bubble Tea.
const (
altScreenMode = 1049
hideCursor = 25
graphemeClustering = 2027
altScreenMode = 1049
hideCursor = 25
)
21 changes: 21 additions & 0 deletions screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,27 @@ func DisableBracketedPaste() Msg {
// disableBracketedPasteMsg with DisableBracketedPaste.
type disableBracketedPasteMsg struct{}

// enableGraphemeClusteringMsg is an internal message that signals that
// grapheme clustering should be enabled.
type enableGraphemeClusteringMsg struct{}

// EnableGraphemeClustering is a special command that tells the Bubble Tea
// program to enable grapheme clustering. This is enabled by default.
func EnableGraphemeClustering() Msg {
return enableGraphemeClusteringMsg{}
}

// disableGraphemeClusteringMsg is an internal message that signals that
// grapheme clustering should be disabled.
type disableGraphemeClusteringMsg struct{}

// DisableGraphemeClustering is a special command that tells the Bubble Tea
// program to disable grapheme clustering. This mode will be disabled
// automatically when the program quits.
func DisableGraphemeClustering() Msg {
return disableGraphemeClusteringMsg{}
}

// enableReportFocusMsg is an internal message that signals that focus
// reporting should be enabled.
type enableReportFocusMsg struct{}
Expand Down
26 changes: 13 additions & 13 deletions screen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,67 +15,67 @@ func TestClearMsg(t *testing.T) {
{
name: "clear_screen",
cmds: []Cmd{ClearScreen},
expected: "\x1b[?25l\x1b[?2004h\x1b[2J\x1b[1;1H\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[2J\x1b[1;1H\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "altscreen",
cmds: []Cmd{EnterAltScreen, ExitAltScreen},
expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25l\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25l\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "altscreen_autoexit",
cmds: []Cmd{EnterAltScreen},
expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[?25l\rsuccess\r\n\x1b[2;0H\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1049l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[?25l\rsuccess\r\n\x1b[2;0H\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1049l\x1b[?25h",
},
{
name: "mouse_cellmotion",
cmds: []Cmd{EnableMouseCellMotion},
expected: "\x1b[?25l\x1b[?2004h\x1b[?1002h\x1b[?1006h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?1002h\x1b[?1006h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "mouse_allmotion",
cmds: []Cmd{EnableMouseAllMotion},
expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?1003h\x1b[?1006h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "mouse_disable",
cmds: []Cmd{EnableMouseAllMotion, DisableMouse},
expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006l\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006l\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "cursor_hide",
cmds: []Cmd{HideCursor},
expected: "\x1b[?25l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "cursor_hideshow",
cmds: []Cmd{HideCursor, ShowCursor},
expected: "\x1b[?25l\x1b[?2004h\x1b[?25h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?25h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l",
},
{
name: "bp_stop_start",
cmds: []Cmd{DisableBracketedPaste, EnableBracketedPaste},
expected: "\x1b[?25l\x1b[?2004h\x1b[?2004l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[?2004l\x1b[?2004h\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "read_set_clipboard",
cmds: []Cmd{ReadClipboard, SetClipboard("success")},
expected: "\x1b[?25l\x1b[?2004h\x1b]52;c;?\a\x1b]52;c;c3VjY2Vzcw==\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b]52;c;?\a\x1b]52;c;c3VjY2Vzcw==\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "bg_fg_cur_color",
cmds: []Cmd{ForegroundColor, BackgroundColor, CursorColor},
expected: "\x1b[?25l\x1b[?2004h\x1b]10;?\a\x1b]11;?\a\x1b]12;?\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b]10;?\a\x1b]11;?\a\x1b]12;?\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "bg_set_color",
cmds: []Cmd{SetBackgroundColor(color.RGBA{255, 255, 255, 255})},
expected: "\x1b[?25l\x1b[?2004h\x1b]11;#ffffff\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b]11;#ffffff\a\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h",
},
{
name: "kitty_start",
cmds: []Cmd{disableKittyKeyboard, enableKittyKeyboard(3)},
expected: "\x1b[?25l\x1b[?2004h\x1b[>u\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>0u",
expected: "\x1b[?25l\x1b[?2004h\x1b[?2027h\x1b[?2027$p\x1b[>u\x1b[>3u\rsuccess\r\n\x1b[D\x1b[2K\r\x1b[?2004l\x1b[?25h\x1b[>0u",
},
}

Expand Down
36 changes: 36 additions & 0 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const (
withKittyKeyboard
withModifyOtherKeys
withWindowsInputMode
withoutGraphemeClustering
)

// channelHandlers manages the series of channels returned by various processes.
Expand Down Expand Up @@ -174,6 +175,8 @@ type Program struct {

bpActive bool // was the bracketed paste mode active before releasing the terminal?

graphemeClustering bool // whether grapheme clustering is enabled

cursorHidden bool // the cursor visibility state

mouseEnabled bool // whether mouse reporting is enabled
Expand Down Expand Up @@ -390,6 +393,16 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
p.suspend()
}

case ReportModeMsg:
switch msg.Mode {
case graphemeClustering:
// 1 means mode is set (see DECRPM).
p.graphemeClustering = msg.Value == 1
if p.graphemeClustering {
p.renderer.SetMode(graphemeClustering, true)
}
}

case clearScreenMsg:
p.renderer.ClearScreen()

Expand Down Expand Up @@ -428,6 +441,19 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
p.execute(ansi.DisableBracketedPaste)
p.bpActive = false

case enableGraphemeClusteringMsg:
p.execute(ansi.EnableGraphemeClustering)
p.execute(ansi.RequestGraphemeClustering)
// We store the state of grapheme clustering after we enable it
// and get a response in the eventLoop.

case disableGraphemeClusteringMsg:
if p.graphemeClustering {
// We only disable grapheme clustering if it was enabled.
p.execute(ansi.DisableGraphemeClustering)
p.renderer.SetMode(graphemeClustering, false)
}

case enableReportFocusMsg:
p.execute(ansi.EnableReportFocus)
p.reportFocus = true
Expand Down Expand Up @@ -701,6 +727,12 @@ func (p *Program) Run() (Model, error) {
p.execute(ansi.EnableBracketedPaste)
p.bpActive = true
}
if p.startupOptions&withoutGraphemeClustering == 0 {
p.execute(ansi.EnableGraphemeClustering)
p.execute(ansi.RequestGraphemeClustering)
// We store the state of grapheme clustering after we query it and get
// a response in the eventLoop.
}
if p.startupOptions&withMouseCellMotion != 0 {
p.execute(ansi.EnableMouseCellMotion)
p.execute(ansi.EnableMouseSgrExt)
Expand Down Expand Up @@ -868,6 +900,7 @@ func (p *Program) ReleaseTerminal() error {

if p.renderer != nil {
p.stopRenderer(false)
// TODO: store these values when they're set in the eventLoop and [Run].
p.altScreenWasActive = p.renderer.Mode(altScreenMode)
p.cursorHidden = p.renderer.Mode(hideCursor)
}
Expand Down Expand Up @@ -925,6 +958,9 @@ func (p *Program) RestoreTerminal() error {
p.execute(ansi.EnableMouseSgrExt)
}
}
if p.graphemeClustering {
p.execute(ansi.EnableGraphemeClustering)
}

// If the output is a terminal, it may have been resized while another
// process was at the foreground, in which case we may not have received
Expand Down
3 changes: 3 additions & 0 deletions tty.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ func (p *Program) restoreTerminalState() error {
if p.reportFocus {
p.execute(ansi.DisableReportFocus)
}
if p.graphemeClustering {
p.execute(ansi.DisableGraphemeClustering)
}

if p.renderer != nil {
if p.renderer.Mode(altScreenMode) {
Expand Down
Loading