Update to tcell v3 (#5562)

Refactor code to introduce a Key type in gocui that bundles the keyName
and a rune, so that we don't have to pass these around separately
everywhere.

This, and updating tcell to v3 lays the ground for supporting more
keybindings in terminals that support the CSIu keyboard protocol.
This commit is contained in:
Stefan Haller 2026-04-30 22:22:30 +02:00 committed by GitHub
commit 69635f379c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
181 changed files with 15816 additions and 8757 deletions

6
go.mod
View file

@ -13,7 +13,7 @@ require (
github.com/cli/go-gh/v2 v2.13.0
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.24
github.com/gdamore/tcell/v2 v2.13.8
github.com/gdamore/tcell/v3 v3.3.0
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.6.0
github.com/integrii/flaggy v1.8.0
@ -47,6 +47,8 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/cli/safeexec v1.0.1 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
@ -67,7 +69,7 @@ require (
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect

12
go.sum
View file

@ -15,6 +15,10 @@ github.com/cli/go-gh/v2 v2.13.0 h1:jEHZu/VPVoIJkciK3pzZd3rbT8J90swsK5Ui4ewH1ys=
github.com/cli/go-gh/v2 v2.13.0/go.mod h1:Us/NbQ8VNM0fdaILgoXSz6PKkV5PWaEzkJdc9vR2geM=
github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -29,8 +33,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
github.com/gdamore/tcell/v3 v3.3.0 h1:lGo3VwiV8iYMH4TdwBDwEOoDctnOLns6QfrXvZkKNe8=
github.com/gdamore/tcell/v3 v3.3.0/go.mod h1:8CJpEjUiAlFIrs7jUhzobiZsSLBAtXo8Dq9mCTrVnqo=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@ -163,8 +167,8 @@ golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

View file

@ -146,7 +146,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
return false
}
return (binding.Description != "" || binding.Alternative != "") && binding.Key != nil
return (binding.Description != "" || binding.Alternative != "") && binding.Key.IsSet()
})
bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header {

View file

@ -3,6 +3,7 @@ package cheatsheet
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/stretchr/testify/assert"
@ -27,7 +28,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
expected: []*bindingSection{
@ -37,7 +38,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -49,7 +50,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "",
Description: "quit",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
expected: []*bindingSection{
@ -59,7 +60,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "",
Description: "quit",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -71,17 +72,17 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "unstage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "submodules",
Description: "drop submodule",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
expected: []*bindingSection{
@ -91,12 +92,12 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "unstage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -106,7 +107,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "submodules",
Description: "drop submodule",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -118,23 +119,23 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "unstage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "scroll",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
{
ViewName: "commits",
Description: "revert commit",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
expected: []*bindingSection{
@ -144,7 +145,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "scroll",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
},
@ -155,7 +156,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "commits",
Description: "revert commit",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -165,12 +166,12 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "unstage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -182,34 +183,34 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "unstage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "scroll",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
{
ViewName: "commits",
Description: "revert commit",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "commits",
Description: "scroll",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
{
ViewName: "commits",
Description: "page up",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
},
@ -220,13 +221,13 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "scroll",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
{
ViewName: "commits",
Description: "page up",
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tag: "navigation",
},
},
@ -237,7 +238,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "commits",
Description: "revert commit",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},
@ -247,12 +248,12 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
ViewName: "files",
Description: "unstage file",
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
},

View file

@ -11,7 +11,7 @@ import (
// NOTE: if you make changes to this table, be sure to update
// docs/keybindings/Custom_Keybindings.md as well
var LabelByKey = map[gocui.Key]string{
var LabelByKey = map[gocui.KeyName]string{
gocui.KeyF1: "<f1>",
gocui.KeyF2: "<f2>",
gocui.KeyF3: "<f3>",
@ -42,8 +42,6 @@ var LabelByKey = map[gocui.Key]string{
gocui.KeyAltEnter: "<a-enter>",
gocui.KeyEsc: "<esc>", // <c-[>, <c-3>
gocui.KeyBackspace: "<backspace>", // <c-h>
gocui.KeyCtrlSpace: "<c-space>", // <c-~>, <c-2>
gocui.KeyCtrlSlash: "<c-/>", // <c-_>
gocui.KeySpace: "<space>",
gocui.KeyCtrlA: "<c-a>",
gocui.KeyCtrlB: "<c-b>",
@ -68,9 +66,6 @@ var LabelByKey = map[gocui.Key]string{
gocui.KeyCtrlX: "<c-x>",
gocui.KeyCtrlY: "<c-y>",
gocui.KeyCtrlZ: "<c-z>",
gocui.KeyCtrl4: "<c-4>", // <c-\>
gocui.KeyCtrl5: "<c-5>", // <c-]>
gocui.KeyCtrl6: "<c-6>",
gocui.KeyCtrl8: "<c-8>",
gocui.MouseWheelUp: "mouse wheel up",
gocui.MouseWheelDown: "mouse wheel down",

View file

@ -4,7 +4,7 @@
package gocui
import "github.com/gdamore/tcell/v2"
import "github.com/gdamore/tcell/v3"
// Attribute affects the presentation of characters, such as color, boldness, etc.
type Attribute uint64

View file

@ -6,64 +6,64 @@ package gocui
// Editor interface must be satisfied by gocui editors.
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier) bool
Edit(v *View, key Key, mod Modifier) bool
}
// The EditorFunc type is an adapter to allow the use of ordinary functions as
// Editors. If f is a function with the appropriate signature, EditorFunc(f)
// is an Editor object that calls f.
type EditorFunc func(v *View, key Key, ch rune, mod Modifier) bool
type EditorFunc func(v *View, key Key, mod Modifier) bool
// Edit calls f(v, key, ch, mod)
func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) bool {
return f(v, key, ch, mod)
// Edit calls f(v, key, mod)
func (f EditorFunc) Edit(v *View, key Key, mod Modifier) bool {
return f(v, key, mod)
}
// DefaultEditor is the default editor.
var DefaultEditor Editor = EditorFunc(SimpleEditor)
// SimpleEditor is used as the default gocui editor.
func SimpleEditor(v *View, key Key, ch rune, mod Modifier) bool {
func SimpleEditor(v *View, key Key, mod Modifier) bool {
switch {
case (key == KeyBackspace || key == KeyBackspace2) && (mod&ModAlt) != 0,
key == KeyCtrlW:
case (key.KeyName() == KeyBackspace || key.KeyName() == KeyBackspace2) && (mod&ModAlt) != 0,
key.KeyName() == KeyCtrlW:
v.TextArea.BackSpaceWord()
case key == KeyBackspace || key == KeyBackspace2 || key == KeyCtrlH:
case key.KeyName() == KeyBackspace || key.KeyName() == KeyBackspace2 || key.KeyName() == KeyCtrlH:
v.TextArea.BackSpaceChar()
case key == KeyCtrlD || key == KeyDelete:
case key.KeyName() == KeyCtrlD || key.KeyName() == KeyDelete:
v.TextArea.DeleteChar()
case key == KeyArrowDown:
case key.KeyName() == KeyArrowDown:
v.TextArea.MoveCursorDown()
case key == KeyArrowUp:
case key.KeyName() == KeyArrowUp:
v.TextArea.MoveCursorUp()
case (key == KeyArrowLeft || ch == 'b') && (mod&ModAlt) != 0:
case (key.KeyName() == KeyArrowLeft || key.Equals(NewKeyRune('b'))) && (mod&ModAlt) != 0:
v.TextArea.MoveLeftWord()
case key == KeyArrowLeft || key == KeyCtrlB:
case key.KeyName() == KeyArrowLeft || key.KeyName() == KeyCtrlB:
v.TextArea.MoveCursorLeft()
case (key == KeyArrowRight || ch == 'f') && (mod&ModAlt) != 0:
case (key.KeyName() == KeyArrowRight || key.Equals(NewKeyRune('f'))) && (mod&ModAlt) != 0:
v.TextArea.MoveRightWord()
case key == KeyArrowRight || key == KeyCtrlF:
case key.KeyName() == KeyArrowRight || key.KeyName() == KeyCtrlF:
v.TextArea.MoveCursorRight()
case key == KeyEnter:
case key.KeyName() == KeyEnter:
v.TextArea.TypeCharacter("\n")
case key == KeySpace:
case key.KeyName() == KeySpace:
v.TextArea.TypeCharacter(" ")
case key == KeyInsert:
case key.KeyName() == KeyInsert:
v.TextArea.ToggleOverwrite()
case key == KeyCtrlU:
case key.KeyName() == KeyCtrlU:
v.TextArea.DeleteToStartOfLine()
case key == KeyCtrlK:
case key.KeyName() == KeyCtrlK:
v.TextArea.DeleteToEndOfLine()
case key == KeyCtrlA || key == KeyHome:
case key.KeyName() == KeyCtrlA || key.KeyName() == KeyHome:
v.TextArea.GoToStartOfLine()
case key == KeyCtrlE || key == KeyEnd:
case key.KeyName() == KeyCtrlE || key.KeyName() == KeyEnd:
v.TextArea.GoToEndOfLine()
case key == KeyCtrlW:
case key.KeyName() == KeyCtrlW:
v.TextArea.BackSpaceWord()
case key == KeyCtrlY:
case key.KeyName() == KeyCtrlY:
v.TextArea.Yank()
case ch != 0:
v.TextArea.TypeCharacter(string(ch))
case key.Str() != "":
v.TextArea.TypeCharacter(key.Str())
default:
return false
}

View file

@ -11,7 +11,7 @@ import (
"sync"
"time"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v3"
"github.com/go-errors/errors"
"github.com/rivo/uniseg"
)
@ -81,14 +81,14 @@ type ViewMouseBinding struct {
Modifier Modifier
// must be a mouse key
Key Key
Key KeyName
}
type ViewMouseBindingOpts struct {
X int // i.e. origin x + cursor x
Y int // i.e. origin y + cursor y
Key Key // which button was clicked (will be one of the Mouse* constants)
Key KeyName // which button was clicked (will be one of the Mouse* constants)
IsDoubleClick bool // true if this is a double click
}
@ -111,7 +111,7 @@ type RecordingConfig struct {
type clickInfo struct {
x int
y int
key Key
key KeyName
viewName string
time time.Time
}
@ -175,14 +175,14 @@ type Gui struct {
Mutexes GuiMutexes
OnSearchEscape func() error
// these keys must either be of type Key of rune
SearchEscapeKey any
NextSearchMatchKey any
PrevSearchMatchKey any
SearchEscapeKey Key
NextSearchMatchKey Key
PrevSearchMatchKey Key
ErrorHandler func(error) error
ShouldHandleMouseEvent func(view *View, key Key) bool
ShouldHandleMouseEvent func(view *View, key KeyName) bool
screen tcell.Screen
suspendedMutex sync.Mutex
@ -254,9 +254,9 @@ func NewGui(opts NewGuiOpts) (*Gui, error) {
g.SupportOverlaps = opts.SupportOverlaps
// default keys for when searching strings in a view
g.SearchEscapeKey = KeyEsc
g.NextSearchMatchKey = 'n'
g.PrevSearchMatchKey = 'N'
g.SearchEscapeKey = NewKeyName(KeyEsc)
g.NextSearchMatchKey = NewKeyRune('n')
g.PrevSearchMatchKey = NewKeyRune('N')
g.playRecording = opts.PlayRecording
@ -549,28 +549,16 @@ func (g *Gui) CurrentView() *View {
// It behaves differently on different platforms. Somewhere it doesn't register Alt key press,
// on others it might report Ctrl as Alt. It's not consistent and therefore it's not recommended
// to use with mouse keys.
func (g *Gui) SetKeybinding(viewname string, key any, mod Modifier, handler func(*Gui, *View) error) error {
var kb *keybinding
k, ch, err := getKey(key)
if err != nil {
return err
}
kb = newKeybinding(viewname, k, ch, mod, handler)
func (g *Gui) SetKeybinding(viewname string, key Key, mod Modifier, handler func(*Gui, *View) error) error {
kb := newKeybinding(viewname, key, mod, handler)
g.keybindings = append(g.keybindings, kb)
return nil
}
// DeleteKeybinding deletes a keybinding.
func (g *Gui) DeleteKeybinding(viewname string, key any, mod Modifier) error {
k, ch, err := getKey(key)
if err != nil {
return err
}
func (g *Gui) DeleteKeybinding(viewname string, key Key, mod Modifier) error {
for i, kb := range g.keybindings {
if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
if kb.viewName == viewname && kb.key.keyName == key.KeyName() && kb.key.str == key.str && kb.mod == mod {
g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
return nil
}
@ -628,21 +616,6 @@ func (g *Gui) SetRenderSearchStatusFunc(renderSearchStatusFunc func(*View, int,
g.renderSearchStatusFunc = renderSearchStatusFunc
}
// getKey takes an empty interface with a key and returns the corresponding
// typed Key or rune.
func getKey(key any) (Key, rune, error) {
switch t := key.(type) {
case nil: // Ignore keybinding if `nil`
return 0, 0, nil
case Key:
return t, 0, nil
case rune:
return 0, t, nil
default:
return 0, 0, errors.New("unknown type")
}
}
// userEvent represents an event triggered by the user.
type userEvent struct {
f func(*Gui) error
@ -1301,8 +1274,8 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
// seem harmful for other terminal emulators.
//
// KeyCtrlJ (int value 10) is '\r'.
if g.IsPasting && ev.Key == KeyCtrlJ {
ev.Key = KeyEnter
if g.IsPasting && ev.Key.KeyName() == KeyCtrlJ {
ev.Key = NewKeyName(KeyEnter)
}
err := g.execKeybindings(g.currentView, ev)
@ -1343,7 +1316,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
}
}
if ev.Key == MouseLeft && (ev.Mod&ModMotion) == 0 && !v.Editable && g.openHyperlink != nil {
if ev.Key.KeyName() == MouseLeft && (ev.Mod&ModMotion) == 0 && !v.Editable && g.openHyperlink != nil {
if newY >= 0 && newY <= len(v.viewLines)-1 && newX >= 0 && newX <= len(v.viewLines[newY].line)-1 {
if link := v.viewLines[newY].line[newX].hyperlink; link != "" {
return g.openHyperlink(link, v.name)
@ -1352,14 +1325,14 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
}
if g.ShouldHandleMouseEvent != nil {
if !g.ShouldHandleMouseEvent(v, ev.Key) {
if !g.ShouldHandleMouseEvent(v, ev.Key.KeyName()) {
// Give clients a chance to reject clicks, for example clicks in inactive views
// when a modal panel is open.
break
}
}
if !IsMouseScrollKey(ev.Key) {
if !IsMouseScrollKey(ev.Key.KeyName()) {
v.SetCursor(newCx, newCy)
if v.Editable {
v.TextArea.SetCursor2D(newX, newY)
@ -1387,8 +1360,8 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
}
if IsMouseKey(ev.Key) {
isDoubleClick := g.recordClickInfo(newX, newY, ev.Key, v)
opts := ViewMouseBindingOpts{X: newX, Y: newY, Key: ev.Key, IsDoubleClick: isDoubleClick}
isDoubleClick := g.recordClickInfo(newX, newY, ev.Key.KeyName(), v)
opts := ViewMouseBindingOpts{X: newX, Y: newY, Key: ev.Key.KeyName(), IsDoubleClick: isDoubleClick}
matched, err := g.execMouseKeybindings(v, ev, opts)
if err != nil {
return err
@ -1422,7 +1395,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
}
// remember the information for this click, and return true if it was a double click
func (g *Gui) recordClickInfo(x, y int, key Key, v *View) bool {
func (g *Gui) recordClickInfo(x, y int, key KeyName, v *View) bool {
if IsMouseScrollKey(key) {
g.lastClick = nil
return false
@ -1450,7 +1423,7 @@ func (g *Gui) recordClickInfo(x, y int, key Key, v *View) bool {
func (g *Gui) execMouseKeybindings(view *View, ev *GocuiEvent, opts ViewMouseBindingOpts) (bool, error) {
isMatch := func(binding *ViewMouseBinding) bool {
return binding.ViewName == view.Name() &&
ev.Key == binding.Key &&
ev.Key.KeyName() == binding.Key &&
ev.Mod == binding.Modifier
}
@ -1472,8 +1445,8 @@ func (g *Gui) execMouseKeybindings(view *View, ev *GocuiEvent, opts ViewMouseBin
return false, nil
}
func IsMouseKey(key any) bool {
switch key {
func IsMouseKey(key Key) bool {
switch key.KeyName() {
case
MouseLeft,
MouseRight,
@ -1489,8 +1462,8 @@ func IsMouseKey(key any) bool {
}
}
func IsMouseScrollKey(key any) bool {
switch key {
func IsMouseScrollKey(keyName KeyName) bool {
switch keyName {
case
MouseWheelUp,
MouseWheelDown,
@ -1514,11 +1487,11 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
if v != nil && v.IsSearching() && ev.Mod == ModNone {
if eventMatchesKey(ev, g.NextSearchMatchKey) {
if ev.Key.Equals(g.NextSearchMatchKey) {
return v.gotoNextMatch()
} else if eventMatchesKey(ev, g.PrevSearchMatchKey) {
} else if ev.Key.Equals(g.PrevSearchMatchKey) {
return v.gotoPreviousMatch()
} else if eventMatchesKey(ev, g.SearchEscapeKey) {
} else if ev.Key.Equals(g.SearchEscapeKey) {
v.searcher.clearSearch()
if g.OnSearchEscape != nil {
if err := g.OnSearchEscape(); err != nil {
@ -1535,7 +1508,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
if kb.handler == nil {
continue
}
if !kb.matchKeypress(ev.Key, ev.Ch, ev.Mod) {
if !kb.matchKeypress(ev.Key, ev.Mod) {
continue
}
if g.matchView(v, kb) {
@ -1550,7 +1523,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
if v != nil && g.matchView(v.ParentView, kb) {
matchingParentViewKb = kb
}
if globalKb == nil && kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) {
if globalKb == nil && kb.viewName == "" && ((v != nil && !v.Editable) || (kb.key.keyName != KeyCtrlU && kb.key.keyName != KeyCtrlA && kb.key.keyName != KeyCtrlE)) {
globalKb = kb
}
}
@ -1562,7 +1535,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
}
if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
matched := g.currentView.Editor.Edit(g.currentView, ev.Key, ev.Ch, ev.Mod)
matched := g.currentView.Editor.Edit(g.currentView, ev.Key, ev.Mod)
if matched {
return nil
}
@ -1622,7 +1595,7 @@ func (g *Gui) matchView(v *View, kb *keybinding) bool {
if v == nil {
return false
}
if v.Editable && kb.ch != 0 {
if v.Editable && kb.key.Str() != "" {
return false
}
if kb.viewName != v.name {

49
pkg/gocui/key.go Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2026 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocui
import "github.com/gdamore/tcell/v3"
type Key struct {
keyName KeyName
str string
}
func NewKey(keyName KeyName, str string) Key {
return Key{
keyName: keyName,
str: str,
}
}
func NewKeyName(keyName KeyName) Key {
return Key{
keyName: keyName,
str: "",
}
}
func NewKeyRune(ch rune) Key {
return Key{
keyName: KeyName(tcell.KeyRune),
str: string(ch),
}
}
func (k Key) KeyName() KeyName {
return k.keyName
}
func (k Key) Str() string {
return k.str
}
func (k Key) IsSet() bool {
return k.keyName != 0
}
func (k Key) Equals(otherKey Key) bool {
return k.keyName == otherKey.keyName && k.str == otherKey.str
}

View file

@ -5,147 +5,119 @@
package gocui
import (
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v3"
)
// Key represents special keys or keys combinations.
type Key tcell.Key
// KeyName represents special keys or keys combinations.
type KeyName tcell.Key
// Modifier allows to define special keys combinations. They can be used
// in combination with Keys or Runes when a new keybinding is defined.
type Modifier tcell.ModMask
// Keybidings are used to link a given key-press event with a handler.
// Keybindings are used to link a given key-press event with a handler.
type keybinding struct {
viewName string
key Key
ch rune
mod Modifier
handler func(*Gui, *View) error
}
// newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
func newKeybinding(viewname string, key Key, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
kb = &keybinding{
viewName: viewname,
key: key,
ch: ch,
mod: mod,
handler: handler,
}
return kb
}
func eventMatchesKey(ev *GocuiEvent, key any) bool {
// assuming ModNone for now
if ev.Mod != ModNone {
return false
}
k, ch, err := getKey(key)
if err != nil {
return false
}
return k == ev.Key && ch == ev.Ch
}
// matchKeypress returns if the keybinding matches the keypress.
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
return kb.key == key && kb.ch == ch && kb.mod == mod
func (kb *keybinding) matchKeypress(key Key, mod Modifier) bool {
return kb.key.Equals(key) && kb.mod == mod
}
// Special keys.
const (
KeyF1 Key = Key(tcell.KeyF1)
KeyF2 = Key(tcell.KeyF2)
KeyF3 = Key(tcell.KeyF3)
KeyF4 = Key(tcell.KeyF4)
KeyF5 = Key(tcell.KeyF5)
KeyF6 = Key(tcell.KeyF6)
KeyF7 = Key(tcell.KeyF7)
KeyF8 = Key(tcell.KeyF8)
KeyF9 = Key(tcell.KeyF9)
KeyF10 = Key(tcell.KeyF10)
KeyF11 = Key(tcell.KeyF11)
KeyF12 = Key(tcell.KeyF12)
KeyInsert = Key(tcell.KeyInsert)
KeyDelete = Key(tcell.KeyDelete)
KeyHome = Key(tcell.KeyHome)
KeyEnd = Key(tcell.KeyEnd)
KeyPgdn = Key(tcell.KeyPgDn)
KeyPgup = Key(tcell.KeyPgUp)
KeyArrowUp = Key(tcell.KeyUp)
KeyShiftArrowUp = Key(tcell.KeyF62)
KeyArrowDown = Key(tcell.KeyDown)
KeyShiftArrowDown = Key(tcell.KeyF63)
KeyArrowLeft = Key(tcell.KeyLeft)
KeyArrowRight = Key(tcell.KeyRight)
KeyF1 KeyName = KeyName(tcell.KeyF1)
KeyF2 = KeyName(tcell.KeyF2)
KeyF3 = KeyName(tcell.KeyF3)
KeyF4 = KeyName(tcell.KeyF4)
KeyF5 = KeyName(tcell.KeyF5)
KeyF6 = KeyName(tcell.KeyF6)
KeyF7 = KeyName(tcell.KeyF7)
KeyF8 = KeyName(tcell.KeyF8)
KeyF9 = KeyName(tcell.KeyF9)
KeyF10 = KeyName(tcell.KeyF10)
KeyF11 = KeyName(tcell.KeyF11)
KeyF12 = KeyName(tcell.KeyF12)
KeyInsert = KeyName(tcell.KeyInsert)
KeyDelete = KeyName(tcell.KeyDelete)
KeyHome = KeyName(tcell.KeyHome)
KeyEnd = KeyName(tcell.KeyEnd)
KeyPgdn = KeyName(tcell.KeyPgDn)
KeyPgup = KeyName(tcell.KeyPgUp)
KeyArrowUp = KeyName(tcell.KeyUp)
KeyShiftArrowUp = KeyName(tcell.KeyF62)
KeyArrowDown = KeyName(tcell.KeyDown)
KeyShiftArrowDown = KeyName(tcell.KeyF63)
KeyArrowLeft = KeyName(tcell.KeyLeft)
KeyArrowRight = KeyName(tcell.KeyRight)
)
// Keys combinations.
const (
KeyCtrlTilde = Key(tcell.KeyF64) // arbitrary assignment
KeyCtrlSpace = Key(tcell.KeyCtrlSpace)
KeyCtrlA = Key(tcell.KeyCtrlA)
KeyCtrlB = Key(tcell.KeyCtrlB)
KeyCtrlC = Key(tcell.KeyCtrlC)
KeyCtrlD = Key(tcell.KeyCtrlD)
KeyCtrlE = Key(tcell.KeyCtrlE)
KeyCtrlF = Key(tcell.KeyCtrlF)
KeyCtrlG = Key(tcell.KeyCtrlG)
KeyBackspace = Key(tcell.KeyBackspace)
KeyCtrlH = Key(tcell.KeyCtrlH)
KeyTab = Key(tcell.KeyTab)
KeyBacktab = Key(tcell.KeyBacktab)
KeyCtrlI = Key(tcell.KeyCtrlI)
KeyCtrlJ = Key(tcell.KeyCtrlJ)
KeyCtrlK = Key(tcell.KeyCtrlK)
KeyCtrlL = Key(tcell.KeyCtrlL)
KeyEnter = Key(tcell.KeyEnter)
KeyCtrlM = Key(tcell.KeyCtrlM)
KeyCtrlN = Key(tcell.KeyCtrlN)
KeyCtrlO = Key(tcell.KeyCtrlO)
KeyCtrlP = Key(tcell.KeyCtrlP)
KeyCtrlQ = Key(tcell.KeyCtrlQ)
KeyCtrlR = Key(tcell.KeyCtrlR)
KeyCtrlS = Key(tcell.KeyCtrlS)
KeyCtrlT = Key(tcell.KeyCtrlT)
KeyCtrlU = Key(tcell.KeyCtrlU)
KeyCtrlV = Key(tcell.KeyCtrlV)
KeyCtrlW = Key(tcell.KeyCtrlW)
KeyCtrlX = Key(tcell.KeyCtrlX)
KeyCtrlY = Key(tcell.KeyCtrlY)
KeyCtrlZ = Key(tcell.KeyCtrlZ)
KeyEsc = Key(tcell.KeyEscape)
KeyCtrlUnderscore = Key(tcell.KeyCtrlUnderscore)
KeySpace = Key(32)
KeyBackspace2 = Key(tcell.KeyBackspace2)
KeyCtrl8 = Key(tcell.KeyBackspace2) // same key as in termbox-go
KeyCtrlTilde = KeyName(tcell.KeyF64) // arbitrary assignment
KeyCtrlA = KeyName(tcell.KeyCtrlA)
KeyCtrlB = KeyName(tcell.KeyCtrlB)
KeyCtrlC = KeyName(tcell.KeyCtrlC)
KeyCtrlD = KeyName(tcell.KeyCtrlD)
KeyCtrlE = KeyName(tcell.KeyCtrlE)
KeyCtrlF = KeyName(tcell.KeyCtrlF)
KeyCtrlG = KeyName(tcell.KeyCtrlG)
KeyBackspace = KeyName(tcell.KeyBackspace)
KeyCtrlH = KeyName(tcell.KeyCtrlH)
KeyTab = KeyName(tcell.KeyTab)
KeyBacktab = KeyName(tcell.KeyBacktab)
KeyCtrlI = KeyName(tcell.KeyCtrlI)
KeyCtrlJ = KeyName(tcell.KeyCtrlJ)
KeyCtrlK = KeyName(tcell.KeyCtrlK)
KeyCtrlL = KeyName(tcell.KeyCtrlL)
KeyEnter = KeyName(tcell.KeyEnter)
KeyCtrlM = KeyName(tcell.KeyCtrlM)
KeyCtrlN = KeyName(tcell.KeyCtrlN)
KeyCtrlO = KeyName(tcell.KeyCtrlO)
KeyCtrlP = KeyName(tcell.KeyCtrlP)
KeyCtrlQ = KeyName(tcell.KeyCtrlQ)
KeyCtrlR = KeyName(tcell.KeyCtrlR)
KeyCtrlS = KeyName(tcell.KeyCtrlS)
KeyCtrlT = KeyName(tcell.KeyCtrlT)
KeyCtrlU = KeyName(tcell.KeyCtrlU)
KeyCtrlV = KeyName(tcell.KeyCtrlV)
KeyCtrlW = KeyName(tcell.KeyCtrlW)
KeyCtrlX = KeyName(tcell.KeyCtrlX)
KeyCtrlY = KeyName(tcell.KeyCtrlY)
KeyCtrlZ = KeyName(tcell.KeyCtrlZ)
KeyEsc = KeyName(tcell.KeyEscape)
KeySpace = KeyName(32)
KeyBackspace2 = KeyName(tcell.KeyBackspace2)
KeyCtrl8 = KeyName(tcell.KeyBackspace2) // same key as in termbox-go
// The following assignments were used in termbox implementation.
// In tcell, these are not keys per se. But in gocui we have them
// mapped to the keys so we have to use placeholder keys.
KeyAltEnter = Key(tcell.KeyF64) // arbitrary assignments
MouseLeft = Key(tcell.KeyF63)
MouseRight = Key(tcell.KeyF62)
MouseMiddle = Key(tcell.KeyF61)
MouseRelease = Key(tcell.KeyF60)
MouseWheelUp = Key(tcell.KeyF59)
MouseWheelDown = Key(tcell.KeyF58)
MouseWheelLeft = Key(tcell.KeyF57)
MouseWheelRight = Key(tcell.KeyF56)
KeyCtrl2 = Key(tcell.KeyNUL) // termbox defines theses
KeyCtrl3 = Key(tcell.KeyEscape)
KeyCtrl4 = Key(tcell.KeyCtrlBackslash)
KeyCtrl5 = Key(tcell.KeyCtrlRightSq)
KeyCtrl6 = Key(tcell.KeyCtrlCarat)
KeyCtrl7 = Key(tcell.KeyCtrlUnderscore)
KeyCtrlSlash = Key(tcell.KeyCtrlUnderscore)
KeyCtrlRsqBracket = Key(tcell.KeyCtrlRightSq)
KeyCtrlBackslash = Key(tcell.KeyCtrlBackslash)
KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq)
KeyAltEnter = KeyName(tcell.KeyF64) // arbitrary assignments
MouseLeft = KeyName(tcell.KeyF63)
MouseRight = KeyName(tcell.KeyF62)
MouseMiddle = KeyName(tcell.KeyF61)
MouseRelease = KeyName(tcell.KeyF60)
MouseWheelUp = KeyName(tcell.KeyF59)
MouseWheelDown = KeyName(tcell.KeyF58)
MouseWheelLeft = KeyName(tcell.KeyF57)
MouseWheelRight = KeyName(tcell.KeyF56)
)
// Modifiers.

View file

@ -5,7 +5,8 @@
package gocui
import (
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v3"
"github.com/gdamore/tcell/v3/vt"
)
// We probably don't want this being a global variable for YOLO for now
@ -83,16 +84,17 @@ func registerRuneFallbacks(s tcell.Screen, additional map[rune]string) {
// tcellInitSimulation initializes tcell screen for use.
func (g *Gui) tcellInitSimulation(width int, height int) error {
s := tcell.NewSimulationScreen("")
if e := s.Init(); e != nil {
mt := vt.NewMockTerm(vt.MockOptSize{X: vt.Col(width), Y: vt.Row(height)})
s, e := tcell.NewTerminfoScreenFromTty(mt)
if e != nil {
return e
}
if e = s.Init(); e != nil {
return e
}
g.screen = s
Screen = s
// setting to a larger value than the typical terminal size
// so that during a test we're more likely to see an item to select in a view.
s.SetSize(width, height)
s.Sync()
return nil
}
@ -163,7 +165,6 @@ type GocuiEvent struct {
Type gocuiEventType
Mod Modifier
Key Key
Ch rune
Width int
Height int
Err error
@ -207,7 +208,7 @@ type TcellKeyEventWrapper struct {
Timestamp int64
Mod tcell.ModMask
Key tcell.Key
Ch rune
Ch string
}
func NewTcellKeyEventWrapper(event *tcell.EventKey, timestamp int64) *TcellKeyEventWrapper {
@ -215,7 +216,7 @@ func NewTcellKeyEventWrapper(event *tcell.EventKey, timestamp int64) *TcellKeyEv
Timestamp: timestamp,
Mod: event.Modifiers(),
Key: event.Key(),
Ch: event.Rune(),
Ch: event.Str(),
}
}
@ -279,7 +280,7 @@ func (g *Gui) pollEvent() GocuiEvent {
tev = (ev).toTcellEvent()
}
} else {
tev = Screen.PollEvent()
tev = <-Screen.EventQ()
}
switch tev := tev.(type) {
@ -290,29 +291,27 @@ func (g *Gui) pollEvent() GocuiEvent {
return GocuiEvent{Type: eventResize, Width: w, Height: h}
case *tcell.EventKey:
k := tev.Key()
ch := rune(0)
ch := ""
if k == tcell.KeyRune {
k = 0 // if rune remove key (so it can match rune instead of key)
ch = tev.Rune()
if ch == ' ' {
ch = tev.Str()
if ch == " " {
// special handling for spacebar
k = 32 // tcell keys ends at 31 or starts at 256
ch = rune(0)
k = tcell.Key(KeySpace)
ch = ""
}
}
mod := tev.Modifiers()
// remove control modifier and setup special handling of ctrl+spacebar, etc.
if mod == tcell.ModCtrl && k == 32 {
mod = 0
ch = rune(0)
k = tcell.KeyCtrlSpace
ch = " "
k = tcell.KeyRune
} else if mod == tcell.ModShift && k == tcell.KeyUp {
mod = 0
ch = rune(0)
ch = ""
k = tcell.KeyF62
} else if mod == tcell.ModShift && k == tcell.KeyDown {
mod = 0
ch = rune(0)
ch = ""
k = tcell.KeyF63
} else if mod == tcell.ModCtrl || mod == tcell.ModShift {
// remove Ctrl or Shift if specified
@ -329,8 +328,7 @@ func (g *Gui) pollEvent() GocuiEvent {
return GocuiEvent{
Type: eventKey,
Key: Key(k),
Ch: ch,
Key: NewKey(KeyName(k), ch),
Mod: Modifier(mod),
}
case *tcell.EventMouse:
@ -413,8 +411,7 @@ func (g *Gui) pollEvent() GocuiEvent {
Type: eventMouse,
MouseX: x,
MouseY: y,
Key: mouseKey,
Ch: 0,
Key: NewKeyName(mouseKey),
Mod: mouseMod,
}
case *tcell.EventFocus:

View file

@ -12,7 +12,7 @@ import (
"unicode"
"unicode/utf8"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v3"
"github.com/rivo/uniseg"
)

View file

@ -7,7 +7,6 @@ import (
"time"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@ -55,7 +54,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader,
keybindings.Label(gui.c.UserConfig().Keybinding.Universal.ExtrasMenu),
gui.c.UserConfig().Keybinding.Universal.ExtrasMenu,
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
@ -72,95 +71,91 @@ func (gui *Gui) printCommandLogHeader() {
func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig().Keybinding
formattedKey := func(key string) string {
return keybindings.Label(key)
}
tips := []string{
// keybindings and lazygit-specific advice
fmt.Sprintf(
"To force push, press '%s' and then if the push is rejected you will be asked if you want to force push",
formattedKey(config.Universal.Push),
config.Universal.Push,
),
fmt.Sprintf(
"To filter commits by path, press '%s'",
formattedKey(config.Universal.FilteringMenu),
config.Universal.FilteringMenu,
),
fmt.Sprintf(
"To start an interactive rebase, press '%s' on a commit. You can always abort the rebase by pressing '%s' and selecting 'abort'",
formattedKey(config.Universal.Edit),
formattedKey(config.Universal.CreateRebaseOptionsMenu),
config.Universal.Edit,
config.Universal.CreateRebaseOptionsMenu,
),
fmt.Sprintf(
"In flat file view, merge conflicts are sorted to the top. To switch to flat file view press '%s'",
formattedKey(config.Files.ToggleTreeView),
config.Files.ToggleTreeView,
),
"If you want to learn Go and can think of ways to improve lazygit, join the team! Click 'Ask Question' and express your interest",
fmt.Sprintf(
"If you press '%s'/'%s' you can undo/redo your changes. Be wary though, this only applies to branches/commits, so only do this if your worktree is clear.\nDocs: %s",
formattedKey(config.Universal.Undo),
formattedKey(config.Universal.Redo),
config.Universal.Undo,
config.Universal.Redo,
constants.Links.Docs.Undoing,
),
fmt.Sprintf(
"to hard reset onto your current upstream branch, press '%s' in the files panel",
formattedKey(config.Commits.ViewResetOptions),
config.Commits.ViewResetOptions,
),
fmt.Sprintf(
"To push a tag, navigate to the tag in the tags tab and press '%s'",
formattedKey(config.Branches.PushTag),
config.Branches.PushTag,
),
fmt.Sprintf(
"You can view the individual files of a stash entry by pressing '%s'",
formattedKey(config.Universal.GoInto),
config.Universal.GoInto,
),
fmt.Sprintf(
"You can diff two commits by pressing '%s' on one commit and then navigating to the other. You can then press '%s' to view the files of the diff",
formattedKey(config.Universal.DiffingMenu),
formattedKey(config.Universal.GoInto),
config.Universal.DiffingMenu,
config.Universal.GoInto,
),
fmt.Sprintf(
"press '%s' on a commit to drop it (delete it)",
formattedKey(config.Universal.Remove),
config.Universal.Remove,
),
fmt.Sprintf(
"If you need to pull out the big guns to resolve merge conflicts, you can press '%s' in the files panel to open merge options",
formattedKey(config.Files.OpenMergeOptions),
config.Files.OpenMergeOptions,
),
fmt.Sprintf(
"To revert a commit, press '%s' on that commit",
formattedKey(config.Commits.RevertCommit),
config.Commits.RevertCommit,
),
fmt.Sprintf(
"To escape a mode, for example cherry-picking, patch-building, diffing, or filtering mode, you can just spam the '%s' button. Unless of course you have `quitOnTopLevelReturn` enabled in your config",
formattedKey(config.Universal.Return),
config.Universal.Return,
),
fmt.Sprintf(
"You can page through the items of a panel using '%s' and '%s'",
formattedKey(config.Universal.PrevPage),
formattedKey(config.Universal.NextPage),
config.Universal.PrevPage,
config.Universal.NextPage,
),
fmt.Sprintf(
"You can jump to the top/bottom of a panel using '%s (or %s)' and '%s (or %s)'",
formattedKey(config.Universal.GotoTop), formattedKey(config.Universal.GotoTopAlt),
formattedKey(config.Universal.GotoBottom), formattedKey(config.Universal.GotoBottomAlt),
config.Universal.GotoTop, config.Universal.GotoTopAlt,
config.Universal.GotoBottom, config.Universal.GotoBottomAlt,
),
fmt.Sprintf(
"To collapse/expand a directory, press '%s'",
formattedKey(config.Universal.GoInto),
config.Universal.GoInto,
),
fmt.Sprintf(
"You can append your staged changes to an older commit by pressing '%s' on that commit",
formattedKey(config.Commits.AmendToCommit),
config.Commits.AmendToCommit,
),
fmt.Sprintf(
"You can amend the last commit with your new file changes by pressing '%s' in the files panel",
formattedKey(config.Files.AmendLastCommit),
config.Files.AmendLastCommit,
),
fmt.Sprintf(
"You can now navigate the side panels with '%s' and '%s'",
formattedKey(config.Universal.NextBlockAlt2),
formattedKey(config.Universal.PrevBlockAlt2),
config.Universal.NextBlockAlt2,
config.Universal.PrevBlockAlt2,
),
"You can use lazygit with a bare repo by passing the --git-dir and --work-tree arguments as you would for the git CLI",

View file

@ -6,7 +6,6 @@ import (
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spf13/afero"
@ -167,8 +166,8 @@ func (self *CommitMessageContext) SetPanelState(
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle,
map[string]string{
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.TogglePanel),
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.CommitMessage.CommitMenu),
"togglePanelKeyBinding": self.c.UserConfig().Keybinding.Universal.TogglePanel,
"commitMenuKeybinding": self.c.UserConfig().Keybinding.CommitMessage.CommitMenu,
})
self.c.Views().CommitDescription.Visible = true

View file

@ -138,7 +138,7 @@ func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
}
keyLabel := ""
if item.Key != nil {
if item.Key.IsSet() {
keyLabel = style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key))
}
@ -205,7 +205,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
basicBindings := self.ListContextTrait.GetKeybindings(opts)
menuItemsWithKeys := lo.Filter(self.menuItems, func(item *types.MenuItem, _ int) bool {
return item.Key != nil
return item.Key.IsSet()
})
menuItemBindings := lo.Map(menuItemsWithKeys, func(item *types.MenuItem, _ int) *types.Binding {

View file

@ -3,7 +3,6 @@ package context
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@ -45,7 +44,7 @@ func (self *SearchTrait) RenderSearchStatus(index int, total int) {
fmt.Sprintf(
self.c.Tr.NoMatchesFor,
self.searchString,
theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)),
theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindingConfig.Universal.Return),
),
)
} else {
@ -58,9 +57,9 @@ func (self *SearchTrait) RenderSearchStatus(index int, total int) {
total,
theme.OptionsFgColor.Sprintf(
self.c.Tr.SearchKeybindings,
keybindings.Label(keybindingConfig.Universal.NextMatch),
keybindings.Label(keybindingConfig.Universal.PrevMatch),
keybindings.Label(keybindingConfig.Universal.Return),
keybindingConfig.Universal.NextMatch,
keybindingConfig.Universal.PrevMatch,
keybindingConfig.Universal.Return,
),
),
)

View file

@ -6,8 +6,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
@ -106,8 +106,8 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Description: self.c.Tr.CherryPickCopy,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.CherryPickCopyTooltip,
map[string]string{
"paste": keybindings.Label(opts.Config.Commits.PasteCommits),
"escape": keybindings.Label(opts.Config.Universal.Return),
"paste": opts.Config.Commits.PasteCommits,
"escape": opts.Config.Universal.Return,
},
),
DisplayOnScreen: true,
@ -164,14 +164,14 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
OnPress: func() error {
return self.copyCommitSubjectToClipboard(commit)
},
Key: 's',
Key: gocui.NewKeyRune('s'),
},
{
Label: self.c.Tr.CommitMessage,
OnPress: func() error {
return self.copyCommitMessageToClipboard(commit)
},
Key: 'm',
Key: gocui.NewKeyRune('m'),
},
{
Label: self.c.Tr.CommitMessageBody,
@ -179,28 +179,28 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
OnPress: func() error {
return self.copyCommitMessageBodyToClipboard(commitMessageBody)
},
Key: 'b',
Key: gocui.NewKeyRune('b'),
},
{
Label: self.c.Tr.CommitURL,
OnPress: func() error {
return self.copyCommitURLToClipboard(commit)
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
},
{
Label: self.c.Tr.CommitDiff,
OnPress: func() error {
return self.copyCommitDiffToClipboard(commit)
},
Key: 'd',
Key: gocui.NewKeyRune('d'),
},
{
Label: self.c.Tr.CommitAuthor,
OnPress: func() error {
return self.copyAuthorToClipboard(commit)
},
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
}
@ -209,7 +209,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
OnPress: func() error {
return self.copyCommitTagsToClipboard(commit)
},
Key: 't',
Key: gocui.NewKeyRune('t'),
}
if len(commit.Tags) == 0 {

View file

@ -6,6 +6,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@ -101,7 +102,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
DisabledReason: singleItemIfNotBisecting,
Key: 'b',
Key: gocui.NewKeyRune('b'),
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortHashToMark, info.OldTerm()),
@ -114,7 +115,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
DisabledReason: singleItemIfNotBisecting,
Key: 'g',
Key: gocui.NewKeyRune('g'),
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortHashToMark),
@ -127,7 +128,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
DisabledReason: singleItemIfNotBisecting,
Key: 's',
Key: gocui.NewKeyRune('s'),
},
}
if info.GetCurrentHash() != "" && info.GetCurrentHash() != commit.Hash() {
@ -142,7 +143,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'S',
Key: gocui.NewKeyRune('S'),
}))
}
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
@ -150,7 +151,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
},
Key: 'r',
Key: gocui.NewKeyRune('r'),
}))
return self.c.Menu(types.CreateMenuOptions{
@ -179,7 +180,7 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'b',
Key: gocui.NewKeyRune('b'),
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortHash(), info.OldTerm()),
@ -197,7 +198,7 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'g',
Key: gocui.NewKeyRune('g'),
},
{
Label: self.c.Tr.Bisect.ChooseTerms,
@ -222,7 +223,7 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
})
return nil
},
Key: 't',
Key: gocui.NewKeyRune('t'),
},
},
})

View file

@ -300,7 +300,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
)
viewDivergenceFromBaseBranchItem := &types.MenuItem{
LabelColumns: []string{label},
Key: 'b',
Key: gocui.NewKeyRune('b'),
OnPress: func() error {
branch := self.context().GetSelected()
if branch == nil {
@ -333,7 +333,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
})
return nil
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
}
setUpstreamItem := &types.MenuItem{
@ -358,7 +358,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
return nil
})
},
Key: 's',
Key: gocui.NewKeyRune('s'),
}
upstreamResetOptions := utils.ResolvePlaceholderString(
@ -391,7 +391,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
return nil
},
Tooltip: upstreamResetTooltip,
Key: 'g',
Key: gocui.NewKeyRune('g'),
}
upstreamRebaseItem := &types.MenuItem{
@ -404,7 +404,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
return nil
},
Tooltip: upstreamRebaseTooltip,
Key: 'r',
Key: gocui.NewKeyRune('r'),
}
if !selectedBranch.IsTrackingRemote() {
@ -624,7 +624,7 @@ func (self *BranchesController) delete(branches []*models.Branch) error {
localDeleteItem := &types.MenuItem{
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalBranches, self.c.Tr.DeleteLocalBranch),
Key: 'c',
Key: gocui.NewKeyRune('c'),
OnPress: func() error {
return self.localDelete(branches)
},
@ -635,7 +635,7 @@ func (self *BranchesController) delete(branches []*models.Branch) error {
remoteDeleteItem := &types.MenuItem{
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteRemoteBranches, self.c.Tr.DeleteRemoteBranch),
Key: 'r',
Key: gocui.NewKeyRune('r'),
OnPress: func() error {
return self.remoteDelete(branches)
},
@ -648,7 +648,7 @@ func (self *BranchesController) delete(branches []*models.Branch) error {
deleteBothItem := &types.MenuItem{
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalAndRemoteBranches, self.c.Tr.DeleteLocalAndRemoteBranch),
Key: 'b',
Key: gocui.NewKeyRune('b'),
OnPress: func() error {
return self.localAndRemoteDelete(branches)
},

View file

@ -2,7 +2,6 @@ package controllers
import (
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -72,18 +71,18 @@ func (self *CommitDescriptionController) GetOnFocus() func(types.OnFocusOpts) {
if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor == "<disabled>" {
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
map[string]string{
"confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt),
"confirmInEditorKeybinding": self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt,
})
} else if self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt == "<disabled>" {
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
map[string]string{
"confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor),
"confirmInEditorKeybinding": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor,
})
} else {
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooterTwoBindings,
map[string]string{
"confirmInEditorKeybinding1": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor),
"confirmInEditorKeybinding2": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt),
"confirmInEditorKeybinding1": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor,
"confirmInEditorKeybinding2": self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt,
})
}
}
@ -120,7 +119,7 @@ func (self *CommitDescriptionController) handleTogglePanel() error {
// which is common in pasted code snippets.
view := self.Context().GetView()
for range 4 {
view.Editor.Edit(view, gocui.KeySpace, ' ', 0)
view.Editor.Edit(view, gocui.NewKeyRune(' '), 0)
}
return nil
}

View file

@ -130,7 +130,7 @@ func (self *CommitMessageController) handleTogglePanel() error {
// switch to the description panel.
view := self.context().GetView()
for range 4 {
view.Editor.Edit(view, gocui.KeySpace, ' ', 0)
view.Editor.Edit(view, gocui.NewKeyRune(' '), 0)
}
return nil
}

View file

@ -14,7 +14,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
@ -231,7 +230,7 @@ func (self *CommitFilesController) openCopyMenu() error {
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'n',
Key: gocui.NewKeyRune('n'),
}
copyRelativePathItem := &types.MenuItem{
Label: self.c.Tr.CopyRelativeFilePath,
@ -243,7 +242,7 @@ func (self *CommitFilesController) openCopyMenu() error {
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'p',
Key: gocui.NewKeyRune('p'),
}
copyAbsolutePathItem := &types.MenuItem{
Label: self.c.Tr.CopyAbsoluteFilePath,
@ -259,7 +258,7 @@ func (self *CommitFilesController) openCopyMenu() error {
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'P',
Key: gocui.NewKeyRune('P'),
}
copyFileDiffItem := &types.MenuItem{
Label: self.c.Tr.CopySelectedDiff,
@ -267,7 +266,7 @@ func (self *CommitFilesController) openCopyMenu() error {
return self.copyDiffToClipboard(node.GetPath(), self.c.Tr.FileDiffCopiedToast)
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 's',
Key: gocui.NewKeyRune('s'),
}
copyAllDiff := &types.MenuItem{
Label: self.c.Tr.CopyAllFilesDiff,
@ -275,7 +274,7 @@ func (self *CommitFilesController) openCopyMenu() error {
return self.copyDiffToClipboard(".", self.c.Tr.AllFilesDiffCopiedToast)
},
DisabledReason: self.require(self.itemsSelected())(),
Key: 'a',
Key: gocui.NewKeyRune('a'),
}
copyFileContentItem := &types.MenuItem{
Label: self.c.Tr.CopyFileContent,
@ -296,7 +295,7 @@ func (self *CommitFilesController) openCopyMenu() error {
}
return nil
}))(),
Key: 'c',
Key: gocui.NewKeyRune('c'),
}
return self.c.Menu(types.CreateMenuOptions{
@ -437,7 +436,7 @@ func (self *CommitFilesController) openDiffTool(node *filetree.CommitFileNode) e
func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.CommitFileNode) error {
if self.c.UserConfig().Git.DiffContextSize == 0 {
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch,
keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView))
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
toggle := func() error {
@ -531,7 +530,7 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
if self.c.UserConfig().Git.DiffContextSize == 0 {
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch,
keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView))
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
from, to, reverse := self.currentFromToReverseForPatchBuilding()

View file

@ -31,19 +31,19 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
Label: self.c.Tr.ResetPatch,
Tooltip: self.c.Tr.ResetPatchTooltip,
OnPress: self.c.Helpers().PatchBuilding.Reset,
Key: 'c',
Key: gocui.NewKeyRune('c'),
},
{
Label: self.c.Tr.ApplyPatch,
Tooltip: self.c.Tr.ApplyPatchTooltip,
OnPress: func() error { return self.handleApplyPatch(false) },
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
Label: self.c.Tr.ApplyPatchInReverse,
Tooltip: self.c.Tr.ApplyPatchInReverseTooltip,
OnPress: func() error { return self.handleApplyPatch(true) },
Key: 'r',
Key: gocui.NewKeyRune('r'),
},
}
@ -53,25 +53,25 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
Label: fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, utils.ShortHash(self.c.Git().Patch.PatchBuilder.To)),
Tooltip: self.c.Tr.RemovePatchFromOriginalCommitTooltip,
OnPress: self.handleDeletePatchFromCommit,
Key: 'd',
Key: gocui.NewKeyRune('d'),
},
{
Label: self.c.Tr.MovePatchOutIntoIndex,
Tooltip: self.c.Tr.MovePatchOutIntoIndexTooltip,
OnPress: self.handleMovePatchIntoWorkingTree,
Key: 'i',
Key: gocui.NewKeyRune('i'),
},
{
Label: self.c.Tr.MovePatchIntoNewCommit,
Tooltip: self.c.Tr.MovePatchIntoNewCommitTooltip,
OnPress: self.handlePullPatchIntoNewCommit,
Key: 'n',
Key: gocui.NewKeyRune('n'),
},
{
Label: self.c.Tr.MovePatchIntoNewCommitBefore,
Tooltip: self.c.Tr.MovePatchIntoNewCommitBeforeTooltip,
OnPress: self.handlePullPatchIntoNewCommitBefore,
Key: 'N',
Key: gocui.NewKeyRune('N'),
},
}...)
@ -93,7 +93,7 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
Label: fmt.Sprintf(self.c.Tr.MovePatchToSelectedCommit, selectedCommit.Hash()),
Tooltip: self.c.Tr.MovePatchToSelectedCommitTooltip,
OnPress: self.handleMovePatchToSelectedCommit,
Key: 'm',
Key: gocui.NewKeyRune('m'),
DisabledReason: disabledReason,
},
}, menuItems[1:]...,
@ -107,7 +107,7 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
{
Label: self.c.Tr.CopyPatchToClipboard,
OnPress: func() error { return self.copyPatchToClipboard() },
Key: 'y',
Key: gocui.NewKeyRune('y'),
},
}...)

View file

@ -671,14 +671,14 @@ func (self *FilesController) handleNonInlineConflict(file *models.File) error {
OnPress: func() error {
return handle(self.c.Git().WorkingTree.StageFile, self.c.Tr.Actions.ResolveConflictByKeepingFile)
},
Key: 'k',
Key: gocui.NewKeyRune('k'),
}
deleteItem := &types.MenuItem{
Label: self.c.Tr.MergeConflictDeleteFile,
OnPress: func() error {
return handle(self.c.Git().WorkingTree.RemoveConflictedFile, self.c.Tr.Actions.ResolveConflictByDeletingFile)
},
Key: 'd',
Key: gocui.NewKeyRune('d'),
}
items := []*types.MenuItem{}
switch file.ShortStatus {
@ -856,7 +856,7 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
}
return nil
},
Key: 'i',
Key: gocui.NewKeyRune('i'),
},
{
LabelColumns: []string{self.c.Tr.ExcludeFile},
@ -866,7 +866,7 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
}
return nil
},
Key: 'e',
Key: gocui.NewKeyRune('e'),
},
},
})
@ -950,7 +950,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayStaged)
},
Key: 's',
Key: gocui.NewKeyRune('s'),
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayStaged),
},
{
@ -958,7 +958,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayUnstaged)
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUnstaged),
},
{
@ -966,7 +966,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayTracked)
},
Key: 't',
Key: gocui.NewKeyRune('t'),
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayTracked),
},
{
@ -974,7 +974,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayUntracked)
},
Key: 'T',
Key: gocui.NewKeyRune('T'),
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUntracked),
},
{
@ -982,7 +982,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayAll)
},
Key: 'r',
Key: gocui.NewKeyRune('r'),
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayAll),
},
},
@ -1092,7 +1092,7 @@ func (self *FilesController) createStashMenu() error {
}
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
},
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
{
Label: self.c.Tr.StashAllChangesKeepIndex,
@ -1103,14 +1103,14 @@ func (self *FilesController) createStashMenu() error {
// if there are no staged files it behaves the same as Stash.Save
return self.handleStashSave(self.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
},
Key: 'i',
Key: gocui.NewKeyRune('i'),
},
{
Label: self.c.Tr.StashIncludeUntrackedChanges,
OnPress: func() error {
return self.handleStashSave(self.c.Git().Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
},
Key: 'U',
Key: gocui.NewKeyRune('U'),
},
{
Label: self.c.Tr.StashStagedChanges,
@ -1121,7 +1121,7 @@ func (self *FilesController) createStashMenu() error {
}
return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
},
Key: 's',
Key: gocui.NewKeyRune('s'),
},
{
Label: self.c.Tr.StashUnstagedChanges,
@ -1135,7 +1135,7 @@ func (self *FilesController) createStashMenu() error {
// ordinary stash
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashUnstagedChanges)
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
},
},
})
@ -1182,7 +1182,7 @@ func (self *FilesController) openCopyMenu() error {
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'n',
Key: gocui.NewKeyRune('n'),
}
copyRelativePathItem := &types.MenuItem{
Label: self.c.Tr.CopyRelativeFilePath,
@ -1194,7 +1194,7 @@ func (self *FilesController) openCopyMenu() error {
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'p',
Key: gocui.NewKeyRune('p'),
}
copyAbsolutePathItem := &types.MenuItem{
Label: self.c.Tr.CopyAbsoluteFilePath,
@ -1210,7 +1210,7 @@ func (self *FilesController) openCopyMenu() error {
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'P',
Key: gocui.NewKeyRune('P'),
}
copyFileDiffItem := &types.MenuItem{
Label: self.c.Tr.CopySelectedDiff,
@ -1236,7 +1236,7 @@ func (self *FilesController) openCopyMenu() error {
return nil
},
))(),
Key: 's',
Key: gocui.NewKeyRune('s'),
}
copyAllDiff := &types.MenuItem{
Label: self.c.Tr.CopyAllFilesDiff,
@ -1261,7 +1261,7 @@ func (self *FilesController) openCopyMenu() error {
return nil
},
)(),
Key: 'a',
Key: gocui.NewKeyRune('a'),
}
return self.c.Menu(types.CreateMenuOptions{
@ -1534,7 +1534,7 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
return nil
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.DiscardUnstagedTooltip,
map[string]string{

View file

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -82,22 +83,22 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
{
Label: "start feature",
OnPress: startHandler("feature"),
Key: 'f',
Key: gocui.NewKeyRune('f'),
},
{
Label: "start hotfix",
OnPress: startHandler("hotfix"),
Key: 'h',
Key: gocui.NewKeyRune('h'),
},
{
Label: "start bugfix",
OnPress: startHandler("bugfix"),
Key: 'b',
Key: gocui.NewKeyRune('b'),
},
{
Label: "start release",
OnPress: startHandler("release"),
Key: 'r',
Key: gocui.NewKeyRune('r'),
},
},
})

View file

@ -228,7 +228,7 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S
OnPress: func() error {
return self.SwitchToEditor()
},
Key: 'e',
Key: gocui.NewKeyRune('e'),
DisabledReason: disabledReasonForOpenInEditor,
},
{
@ -236,14 +236,14 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S
OnPress: func() error {
return self.addCoAuthor(suggestionFunc)
},
Key: 'c',
Key: gocui.NewKeyRune('c'),
},
{
Label: self.c.Tr.PasteCommitMessageFromClipboard,
OnPress: func() error {
return self.pasteCommitMessageFromClipboard()
},
Key: 'p',
Key: gocui.NewKeyRune('p'),
},
}
return self.c.Menu(types.CreateMenuOptions{

View file

@ -39,17 +39,17 @@ const (
func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
type optionAndKey struct {
option string
key types.Key
key gocui.Key
}
options := []optionAndKey{
{option: REBASE_OPTION_CONTINUE, key: 'c'},
{option: REBASE_OPTION_ABORT, key: 'a'},
{option: REBASE_OPTION_CONTINUE, key: gocui.NewKeyRune('c')},
{option: REBASE_OPTION_ABORT, key: gocui.NewKeyRune('a')},
}
if self.c.Git().Status.WorkingTreeState().CanSkip() {
options = append(options, optionAndKey{
option: REBASE_OPTION_SKIP, key: 's',
option: REBASE_OPTION_SKIP, key: gocui.NewKeyRune('s'),
})
}
@ -198,7 +198,7 @@ func (self *MergeAndRebaseHelper) PromptForConflictHandling() error {
OnPress: func() error {
return self.genericMergeCommand(REBASE_OPTION_ABORT)
},
Key: 'a',
Key: gocui.NewKeyRune('a'),
},
},
HideCancel: true,
@ -284,7 +284,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
Label: utils.ResolvePlaceholderString(self.c.Tr.SimpleRebase,
map[string]string{"ref": ref},
),
Key: 's',
Key: gocui.NewKeyRune('s'),
DisabledReason: disabledReason,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
@ -308,7 +308,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
Label: utils.ResolvePlaceholderString(self.c.Tr.InteractiveRebase,
map[string]string{"ref": ref},
),
Key: 'i',
Key: gocui.NewKeyRune('i'),
DisabledReason: disabledReason,
Tooltip: self.c.Tr.InteractiveRebaseTooltip,
OnPress: func() error {
@ -334,7 +334,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
Label: utils.ResolvePlaceholderString(self.c.Tr.RebaseOntoBaseBranch,
map[string]string{"baseBranch": ShortBranchName(baseBranch)},
),
Key: 'b',
Key: gocui.NewKeyRune('b'),
DisabledReason: baseBranchDisabledReason,
Tooltip: self.c.Tr.RebaseOntoBaseBranchTooltip,
OnPress: func() error {
@ -392,7 +392,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
firstRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_REGULAR),
Key: 'm',
Key: gocui.NewKeyRune('m'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeFastForwardTooltip,
map[string]string{
@ -406,7 +406,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
secondRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeNonFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_NON_FAST_FORWARD),
Key: 'n',
Key: gocui.NewKeyRune('n'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeNonFastForwardTooltip,
map[string]string{
@ -419,7 +419,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
firstRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeNonFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_REGULAR),
Key: 'm',
Key: gocui.NewKeyRune('m'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeNonFastForwardTooltip,
map[string]string{
@ -432,7 +432,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
secondRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_FAST_FORWARD),
Key: 'f',
Key: gocui.NewKeyRune('f'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeFastForwardTooltip,
map[string]string{
@ -464,7 +464,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
{
Label: self.c.Tr.SquashMergeUncommitted,
OnPress: self.SquashMergeUncommitted(refName),
Key: 's',
Key: gocui.NewKeyRune('s'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.SquashMergeUncommittedTooltip,
map[string]string{
@ -475,7 +475,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
{
Label: self.c.Tr.SquashMergeCommitted,
OnPress: self.SquashMergeCommitted(refName, checkedOutBranchName),
Key: 'S',
Key: gocui.NewKeyRune('S'),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.SquashMergeCommittedTooltip,
map[string]string{

View file

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -26,8 +25,7 @@ func (self *PatchBuildingHelper) ShowHunkStagingHint() {
self.c.AppState.DidShowHunkStagingHint = true
self.c.SaveAppStateAndLogError()
message := fmt.Sprintf(self.c.Tr.HunkStagingHint,
keybindings.Label(self.c.UserConfig().Keybinding.Main.ToggleSelectHunk))
message := fmt.Sprintf(self.c.Tr.HunkStagingHint, self.c.UserConfig().Keybinding.Main.ToggleSelectHunk)
self.c.Confirm(types.ConfirmOpts{
Prompt: message,
})

View file

@ -216,15 +216,15 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, menuPrompt string, onSelected func(sortOrder string) error, currentValue string) error {
type sortMenuOption struct {
key types.Key
key gocui.Key
label string
description string
sortOrder string
}
availableSortOptions := map[string]sortMenuOption{
"recency": {label: self.c.Tr.SortByRecency, description: self.c.Tr.SortBasedOnReflog, key: 'r'},
"alphabetical": {label: self.c.Tr.SortAlphabetical, description: "--sort=refname", key: 'a'},
"date": {label: self.c.Tr.SortByDate, description: "--sort=-committerdate", key: 'd'},
"recency": {label: self.c.Tr.SortByRecency, description: self.c.Tr.SortBasedOnReflog, key: gocui.NewKeyRune('r')},
"alphabetical": {label: self.c.Tr.SortAlphabetical, description: "--sort=refname", key: gocui.NewKeyRune('a')},
"date": {label: self.c.Tr.SortByDate, description: "--sort=-committerdate", key: gocui.NewKeyRune('d')},
}
sortOptions := make([]sortMenuOption, 0, len(sortOptionsOrder))
for _, key := range sortOptionsOrder {
@ -260,14 +260,14 @@ func (self *RefsHelper) CreateGitResetMenu(name string, ref string) error {
type strengthWithKey struct {
strength string
label string
key types.Key
key gocui.Key
tooltip string
}
strengths := []strengthWithKey{
// not i18'ing because it's git terminology
{strength: "mixed", label: "Mixed reset", key: 'm', tooltip: self.c.Tr.ResetMixedTooltip},
{strength: "soft", label: "Soft reset", key: 's', tooltip: self.c.Tr.ResetSoftTooltip},
{strength: "hard", label: "Hard reset", key: 'h', tooltip: self.c.Tr.ResetHardTooltip},
{strength: "mixed", label: "Mixed reset", key: gocui.NewKeyRune('m'), tooltip: self.c.Tr.ResetMixedTooltip},
{strength: "soft", label: "Soft reset", key: gocui.NewKeyRune('s'), tooltip: self.c.Tr.ResetSoftTooltip},
{strength: "hard", label: "Hard reset", key: gocui.NewKeyRune('h'), tooltip: self.c.Tr.ResetHardTooltip},
}
menuItems := lo.Map(strengths, func(row strengthWithKey, _ int) *types.MenuItem {
@ -312,15 +312,15 @@ func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error {
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
return self.CheckoutRef(hash, types.CheckoutRefOptions{})
},
Key: 'd',
Key: gocui.NewKeyRune('d'),
},
}
if len(branches) > 0 {
menuItems = append(menuItems, lo.Map(branches, func(branch *models.Branch, index int) *types.MenuItem {
var key types.Key
var key gocui.Key
if index < 9 {
key = rune(index + 1 + '0') // Convert 1-based index to key
key = gocui.NewKeyRune(rune(index + 1 + '0')) // Convert 1-based index to key
}
return &types.MenuItem{
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutBranchAtCommit, branch.Name)},
@ -336,7 +336,7 @@ func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error {
LabelColumns: []string{self.c.Tr.Actions.CheckoutBranch},
OnPress: func() error { return nil },
DisabledReason: &types.DisabledReason{Text: self.c.Tr.NoBranchesFoundAtCommitTooltip},
Key: '1',
Key: gocui.NewKeyRune('1'),
})
}

View file

@ -6,7 +6,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
@ -75,7 +74,7 @@ func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext)
promptView := self.promptView()
keybindingConfig := self.c.UserConfig().Keybinding
promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindings.Label(keybindingConfig.Universal.Return)))
promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindingConfig.Universal.Return))
}
func (self *SearchHelper) DisplaySearchStatus(context types.ISearchableContext) {

View file

@ -10,6 +10,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -382,7 +383,7 @@ func (self *WorkingTreeHelper) CreateMergeConflictMenu(selectedFilepaths []strin
OnPress: func() error {
return onMergeStrategySelected("--ours")
},
Key: 'c',
Key: gocui.NewKeyRune('c'),
},
{
LabelColumns: []string{
@ -392,7 +393,7 @@ func (self *WorkingTreeHelper) CreateMergeConflictMenu(selectedFilepaths []strin
OnPress: func() error {
return onMergeStrategySelected("--theirs")
},
Key: 'i',
Key: gocui.NewKeyRune('i'),
},
{
LabelColumns: []string{
@ -402,7 +403,7 @@ func (self *WorkingTreeHelper) CreateMergeConflictMenu(selectedFilepaths []strin
OnPress: func() error {
return onMergeStrategySelected("--union")
},
Key: 'b',
Key: gocui.NewKeyRune('b'),
},
{
LabelColumns: []string{
@ -410,7 +411,7 @@ func (self *WorkingTreeHelper) CreateMergeConflictMenu(selectedFilepaths []strin
cmdColor.Sprint("git mergetool"),
},
OnPress: self.OpenMergeTool,
Key: 'm',
Key: gocui.NewKeyRune('m'),
},
},
})

View file

@ -10,7 +10,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@ -142,7 +141,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
GetDisabledReason: self.require(self.notMidRebase(self.c.Tr.AlreadyRebasing), self.canFindCommitForQuickStart),
Description: self.c.Tr.QuickStartInteractiveRebase,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.QuickStartInteractiveRebaseTooltip, map[string]string{
"editKey": keybindings.Label(editCommitKey),
"editKey": editCommitKey,
}),
},
{
@ -162,7 +161,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.CreateFixupCommitTooltip,
map[string]string{
"squashAbove": keybindings.Label(opts.Config.Commits.SquashAboveCommits),
"squashAbove": opts.Config.Commits.SquashAboveCommits,
},
),
},
@ -362,7 +361,7 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
Items: []*types.MenuItem{
{
Label: self.c.Tr.Fixup,
Key: 'f',
Key: gocui.NewKeyRune('f'),
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
@ -373,7 +372,7 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
},
{
Label: self.c.Tr.FixupKeepMessage,
Key: 'c',
Key: gocui.NewKeyRune('c'),
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage)
@ -404,7 +403,7 @@ func (self *LocalCommitsController) setFixupMessage(commit *models.Commit) error
Items: []*types.MenuItem{
{
Label: self.c.Tr.FixupDiscardMessage,
Key: 'f',
Key: gocui.NewKeyRune('f'),
OnPress: func() error {
return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "")
},
@ -412,7 +411,7 @@ func (self *LocalCommitsController) setFixupMessage(commit *models.Commit) error
},
{
Label: self.c.Tr.FixupKeepMessage,
Key: 'c',
Key: gocui.NewKeyRune('c'),
OnPress: func() error {
return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "-C")
},
@ -680,7 +679,7 @@ func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (
if !ok || index == 0 {
errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{
"editKey": keybindings.Label(self.c.UserConfig().Keybinding.Universal.Edit),
"editKey": self.c.UserConfig().Keybinding.Universal.Edit,
})
return nil, errors.New(errorMsg)
@ -1001,7 +1000,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
Items: []*types.MenuItem{
{
Label: self.c.Tr.FixupMenu_Fixup,
Key: 'f',
Key: gocui.NewKeyRune('f'),
OnPress: func() error {
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
@ -1025,7 +1024,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
},
{
Label: self.c.Tr.FixupMenu_AmendWithChanges,
Key: 'a',
Key: gocui.NewKeyRune('a'),
OnPress: func() error {
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
return self.createAmendCommit(commit, true)
@ -1036,7 +1035,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
},
{
Label: self.c.Tr.FixupMenu_AmendWithoutChanges,
Key: 'r',
Key: gocui.NewKeyRune('r'),
OnPress: func() error { return self.createAmendCommit(commit, false) },
Tooltip: self.c.Tr.FixupMenu_AmendWithoutChangesTooltip,
},
@ -1135,14 +1134,14 @@ func (self *LocalCommitsController) squashFixupCommits() error {
Label: self.c.Tr.SquashCommitsInCurrentBranch,
OnPress: self.squashAllFixupsInCurrentBranch,
DisabledReason: self.canFindCommitForSquashFixupsInCurrentBranch(),
Key: 'b',
Key: gocui.NewKeyRune('b'),
Tooltip: self.c.Tr.SquashCommitsInCurrentBranchTooltip,
},
{
Label: self.c.Tr.SquashCommitsAboveSelectedCommit,
OnPress: self.withItem(self.squashAllFixupsAboveSelectedCommit),
DisabledReason: self.singleItemSelected()(),
Key: 'a',
Key: gocui.NewKeyRune('a'),
Tooltip: self.c.Tr.SquashCommitsAboveSelectedTooltip,
},
},

View file

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
@ -188,7 +187,7 @@ func (self *PatchBuildingController) getDisabledReasonForDiscard() *types.Disabl
}
if self.c.UserConfig().Git.DiffContextSize == 0 {
text := fmt.Sprintf(self.c.Tr.Actions.NotEnoughContextToRemoveLines,
keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView))
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
return &types.DisabledReason{Text: text, ShowErrorInPanel: true}
}
return nil

View file

@ -27,7 +27,7 @@ func NewPromptController(
func (self *PromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: gocui.KeyEnter,
Key: gocui.NewKeyName(gocui.KeyEnter),
Handler: func() error { return self.context().State.OnConfirm() },
Description: self.c.Tr.Confirm,
DisplayOnScreen: true,

View file

@ -24,7 +24,7 @@ func NewSearchPromptController(
func (self *SearchPromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: gocui.KeyEnter,
Key: gocui.NewKeyName(gocui.KeyEnter),
Modifier: gocui.ModNone,
Handler: self.confirm,
},

View file

@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -205,7 +204,7 @@ func (self *StagingController) TogglePanel() error {
func (self *StagingController) ToggleStaged() error {
if self.c.UserConfig().Git.DiffContextSize == 0 {
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToStage,
keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView))
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
return self.applySelectionAndRefresh(self.staged)
@ -214,7 +213,7 @@ func (self *StagingController) ToggleStaged() error {
func (self *StagingController) DiscardSelection() error {
if self.c.UserConfig().Git.DiffContextSize == 0 {
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToDiscard,
keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView))
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
return self.c.ConfirmIf(!self.staged && !self.c.UserConfig().Gui.SkipDiscardChangeWarning,

View file

@ -8,7 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@ -45,7 +44,7 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Enter,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.EnterSubmoduleTooltip,
map[string]string{"escape": keybindings.Label(opts.Config.Universal.Return)}),
map[string]string{"escape": opts.Config.Universal.Return}),
DisplayOnScreen: true,
},
{
@ -95,7 +94,6 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
OpensMenu: true,
},
{
Key: nil,
Handler: self.easterEgg,
Description: self.c.Tr.EasterEgg,
},
@ -235,7 +233,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
return nil
})
},
Key: 'i',
Key: gocui.NewKeyRune('i'),
},
{
LabelColumns: []string{self.c.Tr.BulkUpdateSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateCmdObj().ToString())},
@ -250,7 +248,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
return nil
})
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
},
{
LabelColumns: []string{self.c.Tr.BulkUpdateRecursiveSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateRecursivelyCmdObj().ToString())},
@ -265,7 +263,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
return nil
})
},
Key: 'r',
Key: gocui.NewKeyRune('r'),
},
{
LabelColumns: []string{self.c.Tr.BulkDeinitSubmodules, style.FgRed.Sprint(self.c.Git().Submodule.BulkDeinitCmdObj().ToString())},
@ -280,7 +278,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
return nil
})
},
Key: 'd',
Key: gocui.NewKeyRune('d'),
},
},
})

View file

@ -282,14 +282,14 @@ func (self *TagsController) delete(tag *models.Tag) error {
menuItems := []*types.MenuItem{
{
Label: self.c.Tr.DeleteLocalTag,
Key: 'c',
Key: gocui.NewKeyRune('c'),
OnPress: func() error {
return self.localDelete(tag)
},
},
{
Label: self.c.Tr.DeleteRemoteTag,
Key: 'r',
Key: gocui.NewKeyRune('r'),
OpensMenu: true,
OnPress: func() error {
return self.remoteDelete(tag)
@ -297,7 +297,7 @@ func (self *TagsController) delete(tag *models.Tag) error {
},
{
Label: self.c.Tr.DeleteLocalAndRemoteTag,
Key: 'b',
Key: gocui.NewKeyRune('b'),
OpensMenu: true,
OnPress: func() error {
return self.localAndRemoteDelete(tag)

View file

@ -53,7 +53,7 @@ func (self *FilesController) createResetMenu() error {
})
return nil
},
Key: 'x',
Key: gocui.NewKeyRune('x'),
Tooltip: self.c.Tr.NukeDescription,
},
{
@ -72,7 +72,7 @@ func (self *FilesController) createResetMenu() error {
)
return nil
},
Key: 'u',
Key: gocui.NewKeyRune('u'),
},
{
LabelColumns: []string{
@ -90,7 +90,7 @@ func (self *FilesController) createResetMenu() error {
)
return nil
},
Key: 'c',
Key: gocui.NewKeyRune('c'),
},
{
LabelColumns: []string{
@ -115,7 +115,7 @@ func (self *FilesController) createResetMenu() error {
)
return nil
},
Key: 'S',
Key: gocui.NewKeyRune('S'),
},
{
LabelColumns: []string{
@ -133,7 +133,7 @@ func (self *FilesController) createResetMenu() error {
)
return nil
},
Key: 's',
Key: gocui.NewKeyRune('s'),
},
{
LabelColumns: []string{
@ -151,7 +151,7 @@ func (self *FilesController) createResetMenu() error {
)
return nil
},
Key: 'm',
Key: gocui.NewKeyRune('m'),
},
{
LabelColumns: []string{
@ -176,7 +176,7 @@ func (self *FilesController) createResetMenu() error {
},
})
},
Key: 'h',
Key: gocui.NewKeyRune('h'),
},
}

View file

@ -4,33 +4,33 @@ import (
"github.com/jesseduffield/lazygit/pkg/gocui"
)
func (gui *Gui) handleEditorKeypress(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool {
if key == gocui.KeyEnter && allowMultiline {
func (gui *Gui) handleEditorKeypress(v *gocui.View, key gocui.Key, mod gocui.Modifier, allowMultiline bool) bool {
if key.KeyName() == gocui.KeyEnter && allowMultiline {
v.TextArea.TypeCharacter("\n")
v.RenderTextArea()
return true
}
return gocui.DefaultEditor.Edit(v, key, ch, mod)
return gocui.DefaultEditor.Edit(v, key, mod)
}
// we've just copy+pasted the editor from gocui to here so that we can also re-
// render the commit message length on each keypress
func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, ch, mod, false)
func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, mod, false)
v.RenderTextArea()
gui.c.Contexts().CommitMessage.RenderSubtitle()
return matched
}
func (gui *Gui) commitDescriptionEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, ch, mod, true)
func (gui *Gui) commitDescriptionEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, mod, true)
v.RenderTextArea()
return matched
}
func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, ch, mod, false)
func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, mod, false)
v.RenderTextArea()
@ -46,8 +46,8 @@ func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Mo
return matched
}
func (gui *Gui) searchEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, ch, mod, false)
func (gui *Gui) searchEditor(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
matched := gui.handleEditorKeypress(v, key, mod, false)
v.RenderTextArea()
searchString := v.TextArea.GetContent()

View file

@ -3,6 +3,7 @@ package gui
import (
"io"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -14,7 +15,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
Items: []*types.MenuItem{
{
Label: gui.c.Tr.ToggleShowCommandLog,
Key: 't',
Key: gocui.NewKeyRune('t'),
OnPress: func() error {
currentContext := gui.c.Context().CurrentStatic()
if gui.c.State().GetShowExtrasWindow() && currentContext.GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
@ -29,7 +30,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
},
{
Label: gui.c.Tr.FocusCommandLog,
Key: 'f',
Key: gocui.NewKeyRune('f'),
OnPress: gui.handleFocusCommandLog,
},
},

View file

@ -882,7 +882,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
g.ErrorHandler = gui.PopupHandler.ErrorHandler
gui.g.ShouldHandleMouseEvent = func(view *gocui.View, key gocui.Key) bool {
gui.g.ShouldHandleMouseEvent = func(view *gocui.View, key gocui.KeyName) bool {
if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != view.Name() &&
!gocui.IsMouseScrollKey(key) {
// we ignore click events on views that aren't popup panels, when a popup panel is focused.

View file

@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v3"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gocui"
@ -31,18 +31,8 @@ func (self *GuiDriver) PressKey(keyStr string) {
key := keybindings.GetKey(keyStr)
var r rune
var tcellKey tcell.Key
switch v := key.(type) {
case rune:
r = v
tcellKey = tcell.KeyRune
case gocui.Key:
tcellKey = tcell.Key(v)
}
self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper(
tcell.NewEventKey(tcellKey, r, tcell.ModNone),
tcell.NewEventKey(tcell.Key(key.KeyName()), key.Str(), tcell.ModNone),
0,
)

View file

@ -180,7 +180,7 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin
},
{
ViewName: "information",
Key: gocui.MouseLeft,
Key: gocui.NewKeyName(gocui.MouseLeft),
Modifier: gocui.ModNone,
Handler: gui.handleInfoClick,
},
@ -201,27 +201,27 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin
},
{
ViewName: "main",
Key: gocui.MouseWheelDown,
Key: gocui.NewKeyName(gocui.MouseWheelDown),
Handler: gui.scrollDownMain,
Description: gui.c.Tr.ScrollDown,
Alternative: "fn+up",
},
{
ViewName: "main",
Key: gocui.MouseWheelUp,
Key: gocui.NewKeyName(gocui.MouseWheelUp),
Handler: gui.scrollUpMain,
Description: gui.c.Tr.ScrollUp,
Alternative: "fn+down",
},
{
ViewName: "secondary",
Key: gocui.MouseWheelDown,
Key: gocui.NewKeyName(gocui.MouseWheelDown),
Modifier: gocui.ModNone,
Handler: gui.scrollDownSecondary,
},
{
ViewName: "secondary",
Key: gocui.MouseWheelUp,
Key: gocui.NewKeyName(gocui.MouseWheelUp),
Modifier: gocui.ModNone,
Handler: gui.scrollUpSecondary,
},
@ -251,12 +251,12 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin
},
{
ViewName: "confirmation",
Key: gocui.MouseWheelUp,
Key: gocui.NewKeyName(gocui.MouseWheelUp),
Handler: gui.scrollUpConfirmationPanel,
},
{
ViewName: "confirmation",
Key: gocui.MouseWheelDown,
Key: gocui.NewKeyName(gocui.MouseWheelDown),
Handler: gui.scrollDownConfirmationPanel,
},
{
@ -304,12 +304,12 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin
},
{
ViewName: "extras",
Key: gocui.MouseWheelUp,
Key: gocui.NewKeyName(gocui.MouseWheelUp),
Handler: gui.scrollUpExtra,
},
{
ViewName: "extras",
Key: gocui.MouseWheelDown,
Key: gocui.NewKeyName(gocui.MouseWheelDown),
Handler: gui.scrollDownExtra,
},
{
@ -379,7 +379,7 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin
{
ViewName: "extras",
Tag: "navigation",
Key: gocui.MouseLeft,
Key: gocui.NewKeyName(gocui.MouseLeft),
Modifier: gocui.ModNone,
Handler: gui.handleFocusCommandLog,
},
@ -473,30 +473,12 @@ func (gui *Gui) resetKeybindings() error {
return nil
}
func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
return f()
}
}
func (gui *Gui) SetKeybinding(binding *types.Binding) error {
handler := func() error {
handler := func(g *gocui.Gui, v *gocui.View) error {
return gui.callKeybindingHandler(binding)
}
// TODO: move all mouse-ey stuff into new mouse approach
if gocui.IsMouseKey(binding.Key) {
handler = func() error {
// we ignore click events on views that aren't popup panels, when a popup panel is focused
if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName {
return nil
}
return binding.Handler()
}
}
return gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, gui.wrappedHandler(handler))
return gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, handler)
}
func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error {

View file

@ -1,54 +1,50 @@
package keybindings
import (
"fmt"
"log"
"strings"
"unicode/utf8"
"github.com/gdamore/tcell/v3"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func Label(name string) string {
return LabelFromKey(GetKey(name))
}
func LabelFromKey(key types.Key) string {
if key == nil {
func LabelFromKey(key gocui.Key) string {
if !key.IsSet() {
return ""
}
keyInt := 0
switch key := key.(type) {
case rune:
keyInt = int(key)
case gocui.Key:
value, ok := config.LabelByKey[key]
if ok {
return value
}
keyInt = int(key)
if key.KeyName() == gocui.KeyName(tcell.KeyRune) {
return key.Str()
}
return fmt.Sprintf("%c", keyInt)
value, ok := config.LabelByKey[key.KeyName()]
if ok {
return value
}
return "unknown"
}
func GetKey(key string) types.Key {
runeCount := utf8.RuneCountInString(key)
func GetKey(key string) gocui.Key {
if key == "<disabled>" {
return nil
} else if runeCount > 1 {
binding, ok := config.KeyByLabel[strings.ToLower(key)]
return gocui.Key{}
}
runeCount := utf8.RuneCountInString(key)
if runeCount > 1 {
keyName, ok := config.KeyByLabel[strings.ToLower(key)]
if !ok {
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
}
return binding
} else if runeCount == 1 {
return []rune(key)[0]
return gocui.NewKeyName(keyName)
}
return nil
if runeCount == 1 {
return gocui.NewKeyRune([]rune(key)[0])
}
return gocui.Key{}
}

View file

@ -3,6 +3,7 @@ package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@ -26,7 +27,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
maxColumnSize := 1
essentialKeys := []types.Key{
essentialKeys := []gocui.Key{
keybindings.GetKey(gui.c.UserConfig().Keybinding.Universal.ConfirmMenu),
keybindings.GetKey(gui.c.UserConfig().Keybinding.Universal.Return),
keybindings.GetKey(gui.c.UserConfig().Keybinding.Universal.PrevItem),
@ -46,7 +47,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
// Remove all item keybindings that are the same as one of the essential bindings
if !opts.KeepConflictingKeybindings && lo.Contains(essentialKeys, item.Key) {
item.Key = nil
item.Key = gocui.Key{}
}
}

View file

@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
@ -40,7 +41,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
globalBindings := self.c.Contexts().Global.GetKeybindings(self.c.KeybindingsOpts())
currentContextKeys := set.NewFromSlice(
lo.Map(currentContextBindings, func(binding *types.Binding, _ int) types.Key {
lo.Map(currentContextBindings, func(binding *types.Binding, _ int) gocui.Key {
return binding.Key
}))
@ -69,7 +70,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
if currentContext.GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY {
if self.c.Modes().CherryPicking.Active() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Commits.PasteCommits),
key: self.c.KeybindingsOpts().Config.Commits.PasteCommits,
description: self.c.Tr.PasteCommits,
style: style.FgCyan,
})
@ -77,7 +78,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
if self.c.Model().BisectInfo.Started() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Commits.ViewBisectOptions),
key: self.c.KeybindingsOpts().Config.Commits.ViewBisectOptions,
description: self.c.Tr.ViewBisectOptions,
style: style.FgGreen,
})
@ -87,7 +88,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
// Mode-specific global keybindings
if state := self.c.Model().WorkingTreeStateAtLastCommitRefresh; state.Any() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu),
key: self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu,
description: state.OptionsMapTitle(self.c.Tr),
style: style.FgYellow,
})
@ -95,7 +96,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
if self.c.Git().Patch.PatchBuilder.Active() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreatePatchOptionsMenu),
key: self.c.KeybindingsOpts().Config.Universal.CreatePatchOptionsMenu,
description: self.c.Tr.ViewPatchOptions,
style: style.FgYellow,
})

View file

@ -257,7 +257,7 @@ type MenuItem struct {
// If Key is defined it allows the user to press the key to invoke the menu
// item, as opposed to having to navigate to it
Key Key
Key gocui.Key
// A widget to show in front of the menu item. Supported widget types are
// checkboxes and radio buttons,

View file

@ -239,7 +239,7 @@ type OnFocusLostOpts struct {
type ContextKey string
type KeybindingsOpts struct {
GetKey func(key string) Key
GetKey func(key string) gocui.Key
Config config.KeybindingConfig
Guards KeybindingGuards
}

View file

@ -5,15 +5,13 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/style"
)
type Key any // FIXME: find out how to get `gocui.Key | rune`
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
// is only handled if the given view has focus, or handled globally if the view
// is ""
type Binding struct {
ViewName string
Handler func() error
Key Key
Key gocui.Key
Modifier gocui.Modifier
Description string
// DescriptionFunc is used instead of Description if non-nil, and is useful for dynamic

View file

@ -43,7 +43,7 @@ func RunTUI(raceDetector bool) {
g.SetManagerFunc(app.layout)
if err := g.SetKeybinding("list", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyName(gocui.KeyArrowUp), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if app.itemIdx > 0 {
app.itemIdx--
}
@ -57,7 +57,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyName(gocui.KeyArrowDown), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if app.itemIdx < len(app.filteredTests)-1 {
app.itemIdx++
}
@ -72,15 +72,15 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
if err := g.SetKeybinding("list", gocui.NewKeyName(gocui.KeyCtrlC), gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'q', gocui.ModNone, quit); err != nil {
if err := g.SetKeybinding("list", gocui.NewKeyRune('q'), gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyRune('s'), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
@ -93,7 +93,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyName(gocui.KeyEnter), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
@ -106,7 +106,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 't', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyRune('t'), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
@ -119,7 +119,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyRune('d'), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
@ -132,7 +132,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyRune('o'), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
@ -148,7 +148,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'O', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyRune('O'), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
@ -164,7 +164,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("list", '/', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("list", gocui.NewKeyRune('/'), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
app.filtering = true
if _, err := g.SetCurrentView("editor"); err != nil {
return err
@ -181,7 +181,7 @@ func RunTUI(raceDetector bool) {
}
// not using the editor yet, but will use it to help filter the list
if err := g.SetKeybinding("editor", gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("editor", gocui.NewKeyName(gocui.KeyEsc), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
app.filtering = false
if _, err := g.SetCurrentView("list"); err != nil {
return err
@ -198,7 +198,7 @@ func RunTUI(raceDetector bool) {
log.Panicln(err)
}
if err := g.SetKeybinding("editor", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if err := g.SetKeybinding("editor", gocui.NewKeyName(gocui.KeyEnter), gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
app.filtering = false
if _, err := g.SetCurrentView("list"); err != nil {
@ -273,9 +273,9 @@ func (self *app) renderTests() {
}
}
func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := f(v, key, ch, mod)
func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
return func(v *gocui.View, key gocui.Key, mod gocui.Modifier) bool {
matched := f(v, key, mod)
if matched {
self.filterWithString(v.TextArea.GetContent())
}

View file

@ -0,0 +1,3 @@
.DS_Store
*.out
*.test

51
vendor/github.com/clipperhouse/displaywidth/AGENTS.md generated vendored Normal file
View file

@ -0,0 +1,51 @@
The goals and overview of this package can be found in the README.md file,
start by reading that.
The goal of this package is to determine the display (column) width of a
string, UTF-8 bytes, or runes, as would happen in a monospace font, especially
in a terminal.
When troubleshooting, write Go unit tests instead of executing debug scripts.
The tests can return whatever logs or output you need. If those tests are
only for temporary troubleshooting, clean up the tests after the debugging is
done.
(Separate executable debugging scripts are messy, tend to have conflicting
dependencies and are hard to cleanup.)
If you make changes to the trie generation in internal/gen, it can be invoked
by running `go generate` from the top package directory.
## Pull Requests and branches
For PRs (pull requests), you can use the gh CLI tool. Compare the current branch with main. Reviewing a PR and reviewing a branch are about the same, but the PR may add context.
Understand the goals of the PR. Note any API changes, especially breaking changes.
Look for thoroughness of tests, as well as GoDoc comments.
Retrieve and consider the comments on the PR, which may have come from GitHub Copilot or Cursor BugBot. Think like GitHub Copilot or Cursor BugBot.
Offer to optionally post a brief summary of the review to the PR, via the gh CLI tool.
## Tagged Go releases
If I ask you whether we are ready to release, this means a tagged Go release on the main branch. Go releases are git tagged with a version number.
Review the changes since the last release, i.e. the previous git tag. Ensure that the changes are complete and correct. Identify new features, bug fixes, and performance improvements.
Identify breaking changes, especially API changes.
Ensure good test coverage. Look for performance changes, especially performance regressions, by running benchmarks against the previous release.
Ensure that the documentation in READMEs and GoDocs are complete, correct and consistent.
## Comparisons to go-runewidth
We originally attempted to make this package compatible with go-runewidth.
However, we found that there were too many differences in the handling of
certain characters and properties.
We believe, preliminarily, that our choices are more correct and complete,
by using more complete categories such as Unicode Cf (format) for zero-width
and Mn (Nonspacing_Mark) for combining marks.

View file

@ -0,0 +1,129 @@
# Changelog
## [0.11.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.10.0...v0.11.0)
### Added
- New `ControlSequences8Bit` option to treat 8-bit ECMA-48 (C1) escape sequences as zero-width. (#22)
### Changed
- Upgraded uax29 dependency to v2.7.0 for 8-bit escape sequence support in the grapheme iterator.
- Truncation now validates that preserved trailing escape sequences are zero-width, preventing edge cases where non-zero-width sequences could leak into output.
### Note
- `ControlSequences8Bit` is deliberately ignored by `TruncateString` and `TruncateBytes`, because C1 byte values (0x800x9F) overlap with UTF-8 multi-byte encoding.
## [0.10.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.9.0...v0.10.0)
### Added
- New `ControlSequences` option to treat ECMA-48/ANSI escape sequences as zero-width. (#20)
- `TruncateString` and `TruncateBytes` now preserve trailing ANSI escape sequences (such as SGR resets) when `ControlSequences` is true, preventing color bleed in terminal output.
### Changed
- Removed `stringish` dependency; generic type constraints are now inline `~string | []byte`.
- Upgraded uax29 dependency to v2.6.0 for ANSI escape sequence support in the grapheme iterator.
## [0.9.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.8.0...v0.9.0)
### Changed
- Unicode 17 support: East Asian Width and emoji data updated to Unicode 17.0.0. (#18)
- Upgraded uax29 dependency to v2.5.0 (Unicode 17 grapheme segmentation).
## [0.8.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.7.0...v0.8.0)
### Changed
- Performance: ASCII fast path that applies to any run of printable
ASCII. 2x-10x faster for ASCII text vs v0.7.0. (#16)
- Upgraded uax29 dependency to v2.4.0 for Unicode 16 support. Text that includes
Indic_Conjunct_Break may segment differently (and more correctly). (#15)
## [0.7.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.6.2...v0.7.0)
### Added
- New `TruncateString` and `TruncateBytes` methods to truncate strings to a
maximum display width, with optional tail (like an ellipsis). (#13)
## [0.6.2]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.6.1...v0.6.2)
### Changed
- Internal: reduced property categories for simpler trie.
## [0.6.1]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.6.0...v0.6.1)
### Changed
- Perf improvements: replaced the ASCII lookup table with a simple
function. A bit more cache-friendly. More inlining.
- Bug fix: single regional indicators are now treated as width 2, since that
is what actual terminals do.
## [0.6.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.5.0...v0.6.0)
### Added
- New `StringGraphemes` and `BytesGraphemes` methods, for iterating over the
widths of grapheme clusters.
### Changed
- Fast ASCII lookups
## [0.5.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.4.1...v0.5.0)
### Added
- Unicode 16 support
- Improved emoji presentation handling per Unicode TR51
### Changed
- Corrected VS15 (U+FE0E) handling: now preserves base character width (no-op) per Unicode TR51
- Performance optimizations: reduced property lookups
### Fixed
- VS15 variation selector now correctly preserves base character width instead of forcing width 1
## [0.4.1]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.4.0...v0.4.1)
### Changed
- Updated uax29 dependency
- Improved flag handling
## [0.4.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.3.1...v0.4.0)
### Added
- Support for variation selectors (VS15, VS16) and regional indicator pairs (flags)
## [0.3.1]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.3.0...v0.3.1)
### Added
- Fuzz testing support
### Changed
- Updated stringish dependency
## [0.3.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.2.0...v0.3.0)
### Changed
- Dropped compatibility with go-runewidth
- Trie implementation cleanup

21
vendor/github.com/clipperhouse/displaywidth/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Matt Sherman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

190
vendor/github.com/clipperhouse/displaywidth/README.md generated vendored Normal file
View file

@ -0,0 +1,190 @@
# displaywidth
A high-performance Go package for measuring the monospace display width of strings, UTF-8 bytes, and runes.
[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/displaywidth.svg)](https://pkg.go.dev/github.com/clipperhouse/displaywidth)
[![Test](https://github.com/clipperhouse/displaywidth/actions/workflows/gotest.yml/badge.svg)](https://github.com/clipperhouse/displaywidth/actions/workflows/gotest.yml)
[![Fuzz](https://github.com/clipperhouse/displaywidth/actions/workflows/gofuzz.yml/badge.svg)](https://github.com/clipperhouse/displaywidth/actions/workflows/gofuzz.yml)
## Install
```bash
go get github.com/clipperhouse/displaywidth
```
## Usage
```go
package main
import (
"fmt"
"github.com/clipperhouse/displaywidth"
)
func main() {
width := displaywidth.String("Hello, 世界!")
fmt.Println(width)
width = displaywidth.Bytes([]byte("🌍"))
fmt.Println(width)
width = displaywidth.Rune('🌍')
fmt.Println(width)
}
```
For most purposes, you should use the `String` or `Bytes` methods. They sum
the widths of grapheme clusters in the string or byte slice.
> Note: in your application, iterating over runes to measure width is likely incorrect;
the smallest unit of display is a grapheme, not a rune.
### Iterating over graphemes
If you need the individual graphemes:
```go
import (
"fmt"
"github.com/clipperhouse/displaywidth"
)
func main() {
g := displaywidth.StringGraphemes("Hello, 世界!")
for g.Next() {
width := g.Width()
value := g.Value()
// do something with the width or value
}
}
```
### Options
Create the options you need, and then use methods on the options struct.
```go
var myOptions = displaywidth.Options{
EastAsianWidth: true,
ControlSequences: true,
}
width := myOptions.String("Hello, 世界!")
```
#### ControlSequences
`ControlSequences` specifies whether to ignore ECMA-48 escape sequences
when calculating the display width. When `false` (default), ANSI escape
sequences are treated as just a series of characters. When `true`, they are
treated as a single zero-width unit.
#### ControlSequences8Bit
`ControlSequences8Bit` specifies whether to ignore 8-bit ECMA-48 escape sequences
when calculating the display width. When `false` (default), these are treated
as just a series of characters. When `true`, they are treated as a single
zero-width unit.
Note: this option is ignored by the `Truncate` methods, as the concatenation
can lead to unintended UTF-8 semantics.
#### EastAsianWidth
`EastAsianWidth` defines how
[East Asian Ambiguous characters](https://www.unicode.org/reports/tr11/#Ambiguous)
are treated.
When `false` (default), East Asian Ambiguous characters are treated as width 1.
When `true`, they are treated as width 2.
You may wish to configure this based on environment variables or locale.
`go-runewidth`, for example, does so
[during package initialization](https://github.com/mattn/go-runewidth/blob/master/runewidth.go#L26C1-L45C2). `displaywidth` does not do this automatically, we prefer to leave it to you.
## Technical standards and compatibility
This package implements the Unicode East Asian Width standard
([UAX #11](https://www.unicode.org/reports/tr11/tr11-43.html)), and handles
[version selectors](https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)),
and [regional indicator pairs](https://en.wikipedia.org/wiki/Regional_indicator_symbol)
(flags). We implement [Unicode TR51](https://www.unicode.org/reports/tr51/tr51-27.html)
for emojis. We are keeping an eye on
[emerging standards](https://www.jeffquast.com/post/state-of-terminal-emulation-2025/).
For control sequences, we implement the [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/) standard for 7-bit and 8-bit control sequences.
`clipperhouse/displaywidth`, `mattn/go-runewidth`, and `rivo/uniseg` will
give the same outputs for most real-world text. Extensive details are in the
[compatibility analysis](comparison/COMPATIBILITY_ANALYSIS.md).
## Invalid UTF-8
This package does not validate UTF-8. If you pass invalid UTF-8, the results
are undefined. We fuzz against invalid UTF-8 to ensure we don't panic or
loop indefinitely.
The `ControlSequences8Bit` option means that we will segment valid 8-bit
control sequences, which are typically _not_ valid UTF-8. 8-bit control bytes
happen to also be UTF-8 continuation bytes. Use with caution.
## Prior Art
[mattn/go-runewidth](https://github.com/mattn/go-runewidth)
[rivo/uniseg](https://github.com/rivo/uniseg)
[x/text/width](https://pkg.go.dev/golang.org/x/text/width)
[x/text/internal/triegen](https://pkg.go.dev/golang.org/x/text/internal/triegen)
## Benchmarks
```bash
cd comparison
go test -bench=. -benchmem
```
```
goos: darwin
goarch: arm64
pkg: github.com/clipperhouse/displaywidth/comparison
cpu: Apple M2
BenchmarkString_Mixed/clipperhouse/displaywidth-8 5784 ns/op 291.69 MB/s 0 B/op 0 allocs/op
BenchmarkString_Mixed/mattn/go-runewidth-8 14751 ns/op 114.36 MB/s 0 B/op 0 allocs/op
BenchmarkString_Mixed/rivo/uniseg-8 19360 ns/op 87.14 MB/s 0 B/op 0 allocs/op
BenchmarkString_ASCII/clipperhouse/displaywidth-8 54.60 ns/op 2344.32 MB/s 0 B/op 0 allocs/op
BenchmarkString_ASCII/mattn/go-runewidth-8 1195 ns/op 107.08 MB/s 0 B/op 0 allocs/op
BenchmarkString_ASCII/rivo/uniseg-8 1578 ns/op 81.13 MB/s 0 B/op 0 allocs/op
BenchmarkString_EastAsian/clipperhouse/displaywidth-8 5837 ns/op 289.01 MB/s 0 B/op 0 allocs/op
BenchmarkString_EastAsian/mattn/go-runewidth-8 24418 ns/op 69.09 MB/s 0 B/op 0 allocs/op
BenchmarkString_EastAsian/rivo/uniseg-8 19339 ns/op 87.23 MB/s 0 B/op 0 allocs/op
BenchmarkString_Emoji/clipperhouse/displaywidth-8 3225 ns/op 224.51 MB/s 0 B/op 0 allocs/op
BenchmarkString_Emoji/mattn/go-runewidth-8 4851 ns/op 149.25 MB/s 0 B/op 0 allocs/op
BenchmarkString_Emoji/rivo/uniseg-8 6591 ns/op 109.85 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Mixed/clipperhouse/displaywidth-8 3385 ns/op 498.34 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Mixed/mattn/go-runewidth-8 5354 ns/op 315.07 MB/s 0 B/op 0 allocs/op
BenchmarkRune_EastAsian/clipperhouse/displaywidth-8 3397 ns/op 496.56 MB/s 0 B/op 0 allocs/op
BenchmarkRune_EastAsian/mattn/go-runewidth-8 15673 ns/op 107.64 MB/s 0 B/op 0 allocs/op
BenchmarkRune_ASCII/clipperhouse/displaywidth-8 255.7 ns/op 500.53 MB/s 0 B/op 0 allocs/op
BenchmarkRune_ASCII/mattn/go-runewidth-8 261.5 ns/op 489.55 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Emoji/clipperhouse/displaywidth-8 1371 ns/op 528.22 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Emoji/mattn/go-runewidth-8 2267 ns/op 319.43 MB/s 0 B/op 0 allocs/op
BenchmarkTruncateWithTail/clipperhouse/displaywidth-8 3229 ns/op 54.82 MB/s 192 B/op 14 allocs/op
BenchmarkTruncateWithTail/mattn/go-runewidth-8 8408 ns/op 21.05 MB/s 192 B/op 14 allocs/op
BenchmarkTruncateWithoutTail/clipperhouse/displaywidth-8 3554 ns/op 64.43 MB/s 0 B/op 0 allocs/op
BenchmarkTruncateWithoutTail/mattn/go-runewidth-8 11189 ns/op 20.47 MB/s 0 B/op 0 allocs/op
```
Here are some notes on [how to make Unicode things fast](https://clipperhouse.com/go-unicode/).

3
vendor/github.com/clipperhouse/displaywidth/gen.go generated vendored Normal file
View file

@ -0,0 +1,3 @@
package displaywidth
//go:generate go run -C internal/gen .

View file

@ -0,0 +1,73 @@
package displaywidth
import (
"github.com/clipperhouse/uax29/v2/graphemes"
)
// Graphemes is an iterator over grapheme clusters.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
type Graphemes[T ~string | []byte] struct {
iter *graphemes.Iterator[T]
options Options
}
// Next advances the iterator to the next grapheme cluster.
func (g *Graphemes[T]) Next() bool {
return g.iter.Next()
}
// Value returns the current grapheme cluster.
func (g *Graphemes[T]) Value() T {
return g.iter.Value()
}
// Width returns the display width of the current grapheme cluster.
func (g *Graphemes[T]) Width() int {
return graphemeWidth(g.Value(), g.options)
}
// StringGraphemes returns an iterator over grapheme clusters for the given
// string.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func StringGraphemes(s string) Graphemes[string] {
return DefaultOptions.StringGraphemes(s)
}
// StringGraphemes returns an iterator over grapheme clusters for the given
// string, with the given options.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func (options Options) StringGraphemes(s string) Graphemes[string] {
g := graphemes.FromString(s)
g.AnsiEscapeSequences = options.ControlSequences
g.AnsiEscapeSequences8Bit = options.ControlSequences8Bit
return Graphemes[string]{iter: g, options: options}
}
// BytesGraphemes returns an iterator over grapheme clusters for the given
// []byte.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func BytesGraphemes(s []byte) Graphemes[[]byte] {
return DefaultOptions.BytesGraphemes(s)
}
// BytesGraphemes returns an iterator over grapheme clusters for the given
// []byte, with the given options.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func (options Options) BytesGraphemes(s []byte) Graphemes[[]byte] {
g := graphemes.FromBytes(s)
g.AnsiEscapeSequences = options.ControlSequences
g.AnsiEscapeSequences8Bit = options.ControlSequences8Bit
return Graphemes[[]byte]{iter: g, options: options}
}

30
vendor/github.com/clipperhouse/displaywidth/options.go generated vendored Normal file
View file

@ -0,0 +1,30 @@
package displaywidth
// Options allows you to specify the treatment of ambiguous East Asian
// characters and ANSI escape sequences.
type Options struct {
// EastAsianWidth specifies whether to treat ambiguous East Asian characters
// as width 1 or 2. When false (default), ambiguous East Asian characters
// are treated as width 1. When true, they are width 2.
EastAsianWidth bool
// ControlSequences specifies whether to ignore 7-bit ECMA-48 escape sequences
// when calculating the display width. When false (default), ANSI escape
// sequences are treated as just a series of characters. When true, they are
// treated as a single zero-width unit.
ControlSequences bool
// ControlSequences8Bit specifies whether to ignore 8-bit ECMA-48 escape sequences
// when calculating the display width. When false (default), these are treated
// as just a series of characters. When true, they are treated as a single
// zero-width unit.
ControlSequences8Bit bool
}
// DefaultOptions is the default options for the display width
// calculation, which is EastAsianWidth false, ControlSequences false, and
// ControlSequences8Bit false.
var DefaultOptions = Options{
EastAsianWidth: false,
ControlSequences: false,
ControlSequences8Bit: false,
}

1699
vendor/github.com/clipperhouse/displaywidth/trie.go generated vendored Normal file

File diff suppressed because it is too large Load diff

149
vendor/github.com/clipperhouse/displaywidth/truncate.go generated vendored Normal file
View file

@ -0,0 +1,149 @@
package displaywidth
import (
"strings"
"github.com/clipperhouse/uax29/v2/graphemes"
)
// TruncateString truncates a string to the given maxWidth, and appends the
// given tail if the string is truncated.
//
// It ensures the visible width, including the width of the tail, is less than or
// equal to maxWidth.
//
// When [Options.ControlSequences] is true, 7-bit ANSI escape sequences that
// appear after the truncation point are preserved in the output. This ensures
// that escape sequences such as SGR resets are not lost, preventing color
// bleed in terminal output.
//
// [Options.ControlSequences8Bit] is ignored by truncation. 8-bit C1 byte values
// (0x80-0x9F) overlap with UTF-8 multi-byte encoding, so manipulating them
// during truncation can shift byte boundaries and form unintended visible
// characters. Use [Options.String] or [Options.Bytes] for 8-bit-aware width
// measurement.
func (options Options) TruncateString(s string, maxWidth int, tail string) string {
// We deliberately ignore ControlSequences8Bit for truncation, see above.
options.ControlSequences8Bit = false
maxWidthWithoutTail := maxWidth - options.String(tail)
var pos, total int
g := graphemes.FromString(s)
g.AnsiEscapeSequences = options.ControlSequences
for g.Next() {
gw := graphemeWidth(g.Value(), options)
if total+gw <= maxWidthWithoutTail {
pos = g.End()
}
total += gw
if total > maxWidth {
if options.ControlSequences {
// Build result with trailing 7-bit ANSI escape sequences preserved
var b strings.Builder
b.Grow(len(s) + len(tail)) // at most original + tail
b.WriteString(s[:pos])
b.WriteString(tail)
rem := graphemes.FromString(s[pos:])
rem.AnsiEscapeSequences = options.ControlSequences
for rem.Next() {
v := rem.Value()
// Only preserve 7-bit escapes (ESC = 0x1B) that measure
// as zero-width on their own; some sequences (e.g. SOS)
// are only valid in their original context.
if len(v) > 0 && v[0] == 0x1B && options.String(v) == 0 {
b.WriteString(v)
}
}
return b.String()
}
return s[:pos] + tail
}
}
// No truncation
return s
}
// TruncateString truncates a string to the given maxWidth, and appends the
// given tail if the string is truncated.
//
// It ensures the total width, including the width of the tail, is less than or
// equal to maxWidth.
func TruncateString(s string, maxWidth int, tail string) string {
return DefaultOptions.TruncateString(s, maxWidth, tail)
}
// TruncateBytes truncates a []byte to the given maxWidth, and appends the
// given tail if the []byte is truncated.
//
// It ensures the visible width, including the width of the tail, is less than or
// equal to maxWidth.
//
// When [Options.ControlSequences] is true, 7-bit ANSI escape sequences that
// appear after the truncation point are preserved in the output. This ensures
// that escape sequences such as SGR resets are not lost, preventing color
// bleed in terminal output.
//
// [Options.ControlSequences8Bit] is ignored by truncation. 8-bit C1 byte values
// (0x80-0x9F) overlap with UTF-8 multi-byte encoding, so manipulating them
// during truncation can shift byte boundaries and form unintended visible
// characters. Use [Options.String] or [Options.Bytes] for 8-bit-aware width
// measurement.
func (options Options) TruncateBytes(s []byte, maxWidth int, tail []byte) []byte {
// We deliberately ignore ControlSequences8Bit for truncation, see above.
options.ControlSequences8Bit = false
maxWidthWithoutTail := maxWidth - options.Bytes(tail)
var pos, total int
g := graphemes.FromBytes(s)
g.AnsiEscapeSequences = options.ControlSequences
for g.Next() {
gw := graphemeWidth(g.Value(), options)
if total+gw <= maxWidthWithoutTail {
pos = g.End()
}
total += gw
if total > maxWidth {
if options.ControlSequences {
// Build result with trailing 7-bit ANSI escape sequences preserved
result := make([]byte, 0, len(s)+len(tail)) // at most original + tail
result = append(result, s[:pos]...)
result = append(result, tail...)
rem := graphemes.FromBytes(s[pos:])
rem.AnsiEscapeSequences = options.ControlSequences
for rem.Next() {
v := rem.Value()
// Only preserve 7-bit escapes (ESC = 0x1B) that measure
// as zero-width on their own; some sequences (e.g. SOS)
// are only valid in their original context.
if len(v) > 0 && v[0] == 0x1B && options.Bytes(v) == 0 {
result = append(result, v...)
}
}
return result
}
result := make([]byte, 0, pos+len(tail))
result = append(result, s[:pos]...)
result = append(result, tail...)
return result
}
}
// No truncation
return s
}
// TruncateBytes truncates a []byte to the given maxWidth, and appends the
// given tail if the []byte is truncated.
//
// It ensures the total width, including the width of the tail, is less than or
// equal to maxWidth.
func TruncateBytes(s []byte, maxWidth int, tail []byte) []byte {
return DefaultOptions.TruncateBytes(s, maxWidth, tail)
}

239
vendor/github.com/clipperhouse/displaywidth/width.go generated vendored Normal file
View file

@ -0,0 +1,239 @@
package displaywidth
import (
"unicode/utf8"
"github.com/clipperhouse/uax29/v2/graphemes"
)
// String calculates the display width of a string,
// by iterating over grapheme clusters in the string
// and summing their widths.
func String(s string) int {
return DefaultOptions.String(s)
}
// String calculates the display width of a string, for the given options, by
// iterating over grapheme clusters in the string and summing their widths.
func (options Options) String(s string) int {
width := 0
pos := 0
for pos < len(s) {
// Try ASCII optimization
asciiLen := printableASCIILength(s[pos:])
if asciiLen > 0 {
width += asciiLen
pos += asciiLen
continue
}
// Not ASCII, use grapheme parsing
g := graphemes.FromString(s[pos:])
g.AnsiEscapeSequences = options.ControlSequences
g.AnsiEscapeSequences8Bit = options.ControlSequences8Bit
start := pos
for g.Next() {
v := g.Value()
width += graphemeWidth(v, options)
pos += len(v)
// Quick check: if remaining might have printable ASCII, break to outer loop
if pos < len(s) && s[pos] >= 0x20 && s[pos] <= 0x7E {
break
}
}
// Defensive, should not happen: if no progress was made,
// skip a byte to prevent infinite loop. Only applies if
// the grapheme parser misbehaves.
if pos == start {
pos++
}
}
return width
}
// Bytes calculates the display width of a []byte,
// by iterating over grapheme clusters in the byte slice
// and summing their widths.
func Bytes(s []byte) int {
return DefaultOptions.Bytes(s)
}
// Bytes calculates the display width of a []byte, for the given options, by
// iterating over grapheme clusters in the slice and summing their widths.
func (options Options) Bytes(s []byte) int {
width := 0
pos := 0
for pos < len(s) {
// Try ASCII optimization
asciiLen := printableASCIILength(s[pos:])
if asciiLen > 0 {
width += asciiLen
pos += asciiLen
continue
}
// Not ASCII, use grapheme parsing
g := graphemes.FromBytes(s[pos:])
g.AnsiEscapeSequences = options.ControlSequences
g.AnsiEscapeSequences8Bit = options.ControlSequences8Bit
start := pos
for g.Next() {
v := g.Value()
width += graphemeWidth(v, options)
pos += len(v)
// Quick check: if remaining might have printable ASCII, break to outer loop
if pos < len(s) && s[pos] >= 0x20 && s[pos] <= 0x7E {
break
}
}
// Defensive, should not happen: if no progress was made,
// skip a byte to prevent infinite loop. Only applies if
// the grapheme parser misbehaves.
if pos == start {
pos++
}
}
return width
}
// Rune calculates the display width of a rune. You
// should almost certainly use [String] or [Bytes] for
// most purposes.
//
// The smallest unit of display width is a grapheme
// cluster, not a rune. Iterating over runes to measure
// width is incorrect in many cases.
func Rune(r rune) int {
return DefaultOptions.Rune(r)
}
// Rune calculates the display width of a rune, for the given options.
//
// You should almost certainly use [String] or [Bytes] for most purposes.
//
// The smallest unit of display width is a grapheme cluster, not a rune.
// Iterating over runes to measure width is incorrect in many cases.
func (options Options) Rune(r rune) int {
if r < utf8.RuneSelf {
return asciiWidth(byte(r))
}
// Surrogates (U+D800-U+DFFF) are invalid UTF-8.
if r >= 0xD800 && r <= 0xDFFF {
return 0
}
var buf [4]byte
n := utf8.EncodeRune(buf[:], r)
// Skip the grapheme iterator
return graphemeWidth(buf[:n], options)
}
const _Default property = 0
// graphemeWidth returns the display width of a grapheme cluster.
// The passed string must be a single grapheme cluster.
func graphemeWidth[T ~string | []byte](s T, options Options) int {
if len(s) == 0 {
return 0
}
// C1 controls (0x80-0x9F) are zero-width when 8-bit control sequences
// are enabled. This must be checked before the single-byte optimization
// below, which would otherwise return width 1 for these bytes.
if options.ControlSequences8Bit && s[0] >= 0x80 && s[0] <= 0x9F {
return 0
}
// Optimization: single-byte graphemes need no property lookup
if len(s) == 1 {
return asciiWidth(s[0])
}
// Multi-byte grapheme clusters led by a C0 control (0x00-0x1F)
if s[0] <= 0x1F {
return 0
}
p, sz := lookup(s)
prop := property(p)
// Variation Selector 16 (VS16) requests emoji presentation
if prop != _Wide && sz > 0 && len(s) >= sz+3 {
vs := s[sz : sz+3]
if isVS16(vs) {
prop = _Wide
}
// VS15 (0x8E) requests text presentation but does not affect width,
// in my reading of Unicode TR51. Falls through to return the base
// character's property.
}
if options.EastAsianWidth && prop == _East_Asian_Ambiguous {
prop = _Wide
}
if prop > upperBound {
prop = _Default
}
return propertyWidths[prop]
}
func asciiWidth(b byte) int {
if b <= 0x1F || b == 0x7F {
return 0
}
return 1
}
// printableASCIILength returns the length of consecutive printable ASCII bytes
// starting at the beginning of s.
func printableASCIILength[T string | []byte](s T) int {
i := 0
for ; i < len(s); i++ {
b := s[i]
// Printable ASCII is 0x20-0x7E (space through tilde)
if b < 0x20 || b > 0x7E {
break
}
}
// If the next byte is non-ASCII (>= 0x80), back off by 1. The grapheme
// parser may group the last ASCII byte with subsequent non-ASCII bytes,
// such as combining marks.
if i > 0 && i < len(s) && s[i] >= 0x80 {
i--
}
return i
}
// isVS16 checks if the slice matches VS16 (U+FE0F) UTF-8 encoding
// (EF B8 8F). It assumes len(s) >= 3.
func isVS16[T ~string | []byte](s T) bool {
return s[0] == 0xEF && s[1] == 0xB8 && s[2] == 0x8F
}
// propertyWidths is a jump table of sorts, instead of a switch
var propertyWidths = [4]int{
_Default: 1,
_Zero_Width: 0,
_Wide: 2,
_East_Asian_Ambiguous: 1,
}
const upperBound = property(len(propertyWidths) - 1)

21
vendor/github.com/clipperhouse/uax29/v2/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Matt Sherman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,120 @@
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode 17.
[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/uax29/v2/graphemes.svg)](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
![Tests](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
![Fuzz](https://github.com/clipperhouse/uax29/actions/workflows/gofuzz.yml/badge.svg)
## Quick start
```
go get github.com/clipperhouse/uax29/v2/graphemes
```
```go
import "github.com/clipperhouse/uax29/v2/graphemes"
text := "Hello, 世界. Nice dog! 👍🐶"
g := graphemes.FromString(text)
for g.Next() { // Next() returns true until end of data
fmt.Println(g.Value()) // Do something with the current grapheme
}
```
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
## Conformance
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-36.html#Tests29).
![Tests](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
![Fuzz](https://github.com/clipperhouse/uax29/actions/workflows/gofuzz.yml/badge.svg)
## APIs
### If you have a `string`
```go
text := "Hello, 世界. Nice dog! 👍🐶"
g := graphemes.FromString(text)
for g.Next() { // Next() returns true until end of data
fmt.Println(g.Value()) // Do something with the current grapheme
}
```
### If you have an `io.Reader`
`FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods.
```go
r := getYourReader() // from a file or network maybe
g := graphemes.FromReader(r)
for g.Scan() { // Scan() returns true until error or EOF
fmt.Println(g.Text()) // Do something with the current grapheme
}
if g.Err() != nil { // Check the error
log.Fatal(g.Err())
}
```
### If you have a `[]byte`
```go
b := []byte("Hello, 世界. Nice dog! 👍🐶")
g := graphemes.FromBytes(b)
for g.Next() { // Next() returns true until end of data
fmt.Println(g.Value()) // Do something with the current grapheme
}
```
### ANSI escape sequences
By the UAX 29 specification, ANSI escape sequences are not grapheme clusters. To treat 7-bit ANSI escape sequences as a single cluster, set `AnsiEscapeSequences` to true.
```go
text := "Hello, \x1b[31mworld\x1b[0m!"
g := graphemes.FromString(text)
g.AnsiEscapeSequences = true
for g.Next() {
fmt.Println(g.Value())
}
```
To also parse 8-bit C1 controls (non-UTF-8 bytes), set `AnsiEscapeSequences8Bit` to true.
```go
g.AnsiEscapeSequences = true // 7-bit forms (ESC ...)
g.AnsiEscapeSequences8Bit = true // 8-bit C1 forms (0x80-0x9F), not valid UTF-8
```
For ESC-initiated (7-bit) control strings, only 7-bit terminators are recognized.
For C1-initiated (8-bit) control strings, only C1 ST (`0x9C`) is recognized as ST.
We implement [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/) control codes in both 7-bit and 8-bit representations. 8-bit control codes are not UTF-8 encoded and are not valid UTF-8, caveat emptor.
### Benchmarks
```
goos: darwin
goarch: arm64
pkg: github.com/clipperhouse/uax29/graphemes/comparative
cpu: Apple M2
BenchmarkGraphemesMixed/clipperhouse/uax29-8 142635 ns/op 245.12 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesMixed/rivo/uniseg-8 2018284 ns/op 17.32 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesASCII/clipperhouse/uax29-8 8846 ns/op 508.73 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesASCII/rivo/uniseg-8 366760 ns/op 12.27 MB/s 0 B/op 0 allocs/op
```
### Invalid inputs
Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”.
Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid).

View file

@ -0,0 +1,138 @@
package graphemes
// ansiEscapeLength returns the byte length of a valid 7-bit ANSI escape
// sequence at the start of data, or 0 if none.
//
// Recognized forms (ECMA-48 / ISO 6429):
// - CSI: ESC [ then parameter bytes (0x30-0x3F), intermediate (0x20-0x2F), final (0x40-0x7E)
// - OSC: ESC ] then payload until BEL (0x07), 7-bit ST (ESC \), CAN (0x18), or SUB (0x1A)
// - DCS, SOS, PM, APC: ESC P/X/^/_ then payload until 7-bit ST (ESC \), CAN, or SUB
// - Two-byte: ESC + Fe/Fs (0x40-0x7E excluding above), or Fp (0x30-0x3F), or nF (0x20-0x2F then final)
func ansiEscapeLength[T ~string | ~[]byte](data T) int {
n := len(data)
if n < 2 || data[0] != esc {
return 0
}
b1 := data[1]
switch b1 {
case '[': // CSI
body := csiBodyLength(data[2:])
if body == 0 {
return 0
}
return 2 + body
case ']': // OSC - allows BEL or 7-bit ST terminator
body := oscLength(data[2:])
if body < 0 {
return 0
}
return 2 + body
case 'P', 'X', '^', '_': // DCS, SOS, PM, APC
body := stSequenceLength(data[2:])
if body < 0 {
return 0
}
return 2 + body
}
if b1 >= 0x40 && b1 <= 0x7E {
// Fe/Fs two-byte; [ ] P X ^ _ handled above
return 2
}
if b1 >= 0x30 && b1 <= 0x3F {
// Fp (private) two-byte
return 2
}
if b1 >= 0x20 && b1 <= 0x2F {
// nF: intermediates then one final (0x30-0x7E)
i := 2
for i < n && data[i] >= 0x20 && data[i] <= 0x2F {
i++
}
if i < n && data[i] >= 0x30 && data[i] <= 0x7E {
return i + 1
}
return 0
}
return 0
}
// csiBodyLength returns the length of the CSI body (param/intermediate/final bytes).
// data is the slice after "ESC [".
// Per ECMA-48, the CSI body has the form:
//
// parameters (0x300x3F)*, intermediates (0x200x2F)*, final (0x400x7E)
//
// Once an intermediate byte is seen, subsequent parameter bytes are invalid.
func csiBodyLength[T ~string | ~[]byte](data T) int {
seenIntermediate := false
for i := 0; i < len(data); i++ {
b := data[i]
if b >= 0x30 && b <= 0x3F {
if seenIntermediate {
return 0
}
continue
}
if b >= 0x20 && b <= 0x2F {
seenIntermediate = true
continue
}
if b >= 0x40 && b <= 0x7E {
return i + 1
}
return 0
}
return 0
}
// oscLength returns the length of the OSC body.
// data is the slice after "ESC ]".
//
// Returns:
// - n >= 0: consumed body length (includes BEL/ST terminator when present)
// - -1: not terminated in the provided data
//
// OSC accepts BEL (0x07) or 7-bit ST (ESC \) as terminators by widespread convention.
// Per ECMA-48, CAN (0x18) and SUB (0x1A) cancel the control string; in that
// case they are not part of the OSC sequence length.
func oscLength[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
b := data[i]
if b == bel {
return i + 1
}
if b == can || b == sub {
return i
}
if b == esc && i+1 < len(data) && data[i+1] == '\\' {
return i + 2
}
}
return -1
}
// stSequenceLength returns the length of a control-string body.
// data is the slice after "ESC x".
//
// Returns:
// - n >= 0: consumed body length (includes ST terminator when present)
// - -1: not terminated in the provided data
//
// Used for DCS, SOS, PM, and APC, which per ECMA-48 terminate with ST.
// ST here is the 7-bit form (ESC \).
// CAN (0x18) and SUB (0x1A) cancel the control string; in that case they are
// not part of the sequence length.
func stSequenceLength[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
if data[i] == can || data[i] == sub {
return i
}
if data[i] == esc && i+1 < len(data) && data[i+1] == '\\' {
return i + 2
}
}
return -1
}

View file

@ -0,0 +1,79 @@
package graphemes
// ansiEscapeLength8Bit returns the byte length of a valid 8-bit C1 ANSI
// sequence at the start of data, or 0 if none.
//
// Recognized forms (ECMA-48 / ISO 6429):
// - C1 CSI (0x9B) body as parameter/intermediate/final bytes
// - C1 OSC (0x9D) body terminated by BEL, C1 ST, CAN, or SUB
// - C1 DCS/SOS/PM/APC (0x90/0x98/0x9E/0x9F) body terminated by C1 ST, CAN, or SUB
// - Standalone C1 controls (0x80..0x9F not listed above): single byte
func ansiEscapeLength8Bit[T ~string | ~[]byte](data T) int {
if len(data) == 0 {
return 0
}
switch data[0] {
case 0x9B: // C1 CSI
body := csiBodyLength(data[1:])
if body == 0 {
return 0
}
return 1 + body
case 0x9D: // C1 OSC
body := oscLengthC1(data[1:])
if body < 0 {
return 0
}
return 1 + body
case 0x90, 0x98, 0x9E, 0x9F: // C1 DCS, SOS, PM, APC
body := stSequenceLengthC1(data[1:])
if body < 0 {
return 0
}
return 1 + body
default:
if data[0] >= 0x80 && data[0] <= 0x9F {
return 1
}
}
return 0
}
// oscLengthC1 returns the length of a C1 OSC body.
// data is the slice after the C1 OSC initiator (0x9D).
//
// Returns:
// - n >= 0: consumed body length (includes BEL/ST terminator when present)
// - -1: not terminated in the provided data
//
// Terminators: BEL (0x07) or C1 ST (0x9C).
// CAN (0x18) and SUB (0x1A) cancel the control string.
func oscLengthC1[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
b := data[i]
if b == bel || b == st {
return i + 1
}
if b == can || b == sub {
return i
}
}
return -1
}
// stSequenceLengthC1 parses DCS/SOS/PM/APC bodies that terminate with C1 ST
// (0x9C), or are canceled by CAN/SUB.
func stSequenceLengthC1[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
b := data[i]
if b == can || b == sub {
return i
}
if b == st {
return i + 1
}
}
return -1
}

View file

@ -0,0 +1,144 @@
package graphemes
import "unicode/utf8"
// FromString returns an iterator for the grapheme clusters in the input string.
// Iterate while Next() is true, and access the grapheme via Value().
func FromString(s string) *Iterator[string] {
return &Iterator[string]{
split: splitFuncString,
data: s,
}
}
// FromBytes returns an iterator for the grapheme clusters in the input bytes.
// Iterate while Next() is true, and access the grapheme via Value().
func FromBytes(b []byte) *Iterator[[]byte] {
return &Iterator[[]byte]{
split: splitFuncBytes,
data: b,
}
}
// Iterator is a generic iterator for grapheme clusters in strings or byte slices,
// with an ASCII hot path optimization.
type Iterator[T ~string | ~[]byte] struct {
split func(T, bool) (int, T, error)
data T
pos int
start int
// AnsiEscapeSequences treats 7-bit ANSI escape sequences (ECMA-48) as
// single grapheme clusters when true. The default is false.
//
// 8-bit controls are not enabled by this option. See [AnsiEscapeSequences8Bit].
AnsiEscapeSequences bool
// AnsiEscapeSequences8Bit treats 8-bit C1 ANSI escape sequences (ECMA-48) as single
// grapheme clusters when true. The default is false.
//
// 8-bit control bytes are not UTF-8 encoded, i.e. not valid UTF-8. If you
// choose this option, you are choosing to interpret non-UTF-8 data, caveat
// emptor.
AnsiEscapeSequences8Bit bool
}
var (
splitFuncString = splitFunc[string]
splitFuncBytes = splitFunc[[]byte]
)
const (
esc = 0x1B
cr = 0x0D
bel = 0x07
can = 0x18
sub = 0x1A
st = 0x9C
)
// Next advances the iterator to the next grapheme cluster.
// Returns false when there are no more grapheme clusters.
func (iter *Iterator[T]) Next() bool {
if iter.pos >= len(iter.data) {
return false
}
iter.start = iter.pos
b := iter.data[iter.pos]
if iter.AnsiEscapeSequences && b == esc {
if a := ansiEscapeLength(iter.data[iter.pos:]); a > 0 {
iter.pos += a
return true
}
}
if iter.AnsiEscapeSequences8Bit && b >= 0x80 && b <= 0x9F {
if a := ansiEscapeLength8Bit(iter.data[iter.pos:]); a > 0 {
iter.pos += a
return true
}
}
// ASCII hot path: any ASCII is one grapheme when next byte is ASCII or end.
if b < utf8.RuneSelf && b != cr {
if iter.pos+1 >= len(iter.data) || iter.data[iter.pos+1] < utf8.RuneSelf {
iter.pos++
return true
}
}
// Fall back to UAX29 grapheme parsing
remaining := iter.data[iter.pos:]
advance, _, err := iter.split(remaining, true)
if err != nil {
panic(err)
}
if advance <= 0 {
panic("splitFunc returned a zero or negative advance")
}
iter.pos += advance
if iter.pos > len(iter.data) {
panic("splitFunc advanced beyond end of data")
}
return true
}
// Value returns the current grapheme cluster.
func (iter *Iterator[T]) Value() T {
return iter.data[iter.start:iter.pos]
}
// Start returns the byte position of the current grapheme in the original data.
func (iter *Iterator[T]) Start() int {
return iter.start
}
// End returns the byte position after the current grapheme in the original data.
func (iter *Iterator[T]) End() int {
return iter.pos
}
// Reset resets the iterator to the beginning of the data.
func (iter *Iterator[T]) Reset() {
iter.start = 0
iter.pos = 0
}
// SetText sets the data for the iterator to operate on, and resets all state.
func (iter *Iterator[T]) SetText(data T) {
iter.data = data
iter.start = 0
iter.pos = 0
}
// First returns the first grapheme cluster without advancing the iterator.
func (iter *Iterator[T]) First() T {
if len(iter.data) == 0 {
return iter.data
}
// Use a copy to leverage Next()'s ASCII optimization
cp := *iter
cp.pos = 0
cp.start = 0
cp.Next()
return cp.Value()
}

View file

@ -0,0 +1,25 @@
// Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
package graphemes
import (
"bufio"
"io"
)
type Scanner struct {
*bufio.Scanner
}
// FromReader returns a Scanner, to split graphemes per
// https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
//
// It embeds a [bufio.Scanner], so you can use its methods.
//
// Iterate through graphemes by calling Scan() until false, then check Err().
func FromReader(r io.Reader) *Scanner {
sc := bufio.NewScanner(r)
sc.Split(SplitFunc)
return &Scanner{
Scanner: sc,
}
}

View file

@ -0,0 +1,205 @@
package graphemes
import (
"bufio"
)
// is determines if lookup intersects propert(ies)
func (lookup property) is(properties property) bool {
return (lookup & properties) != 0
}
const _Ignore = _Extend
// incbState tracks state for GB9c rule (Indic conjunct clusters)
// Pattern: Consonant (Extend|Linker)* Linker (Extend|Linker)* × Consonant
type incbState int
const (
incbNone incbState = iota // initial/reset
incbConsonant // seen Consonant, awaiting Linker
incbLinker // seen Consonant and Linker (conjunct ready)
)
// SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner.
//
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
func splitFunc[T ~string | ~[]byte](data T, atEOF bool) (advance int, token T, err error) {
var empty T
if len(data) == 0 {
return 0, empty, nil
}
// These vars are stateful across loop iterations
var pos int
var lastExIgnore property = 0 // "last excluding ignored categories"
var lastLastExIgnore property = 0 // "last one before that"
var regionalIndicatorCount int
// GB9c state: tracking Indic conjunct clusters
var incb incbState
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
// to the right of the ×, from which we look back or forward
current, w := lookup(data[pos:])
if w == 0 {
if !atEOF {
// Rune extends past current data, request more
return 0, empty, nil
}
pos = len(data)
return pos, data[:pos], nil
}
// https://unicode.org/reports/tr29/#GB1
// Start of text always advances
pos += w
for {
eot := pos == len(data) // "end of text"
if eot {
if !atEOF {
// Token extends past current data, request more
return 0, empty, nil
}
// https://unicode.org/reports/tr29/#GB2
break
}
/*
We've switched the evaluation order of GB1 and GB2. It's ok:
because we've checked for len(data) at the top of this function,
sot and eot are mutually exclusive, order doesn't matter.
*/
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
// to the right of the ×, from which we look back or forward
// Remember previous properties to avoid lookups/lookbacks
last := current
if !last.is(_Ignore) {
lastLastExIgnore = lastExIgnore
lastExIgnore = last
}
// Update GB9c state based on what we just advanced past
if last.is(_InCBConsonant | _InCBLinker | _InCBExtend) {
switch {
case last.is(_InCBConsonant):
if incb != incbLinker {
incb = incbConsonant
}
case last.is(_InCBLinker):
if incb >= incbConsonant {
incb = incbLinker
}
// case last.is(_InCBExtend): stay in current state
}
} else {
incb = incbNone
}
current, w = lookup(data[pos:])
if w == 0 {
if atEOF {
// Just return the bytes, we can't do anything with them
pos = len(data)
break
}
// Rune extends past current data, request more
return 0, empty, nil
}
// Optimization: no rule can possibly apply
if current|last == 0 { // i.e. both are zero
break
}
// https://unicode.org/reports/tr29/#GB3
if current.is(_LF) && last.is(_CR) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB4
// https://unicode.org/reports/tr29/#GB5
if (current | last).is(_Control | _CR | _LF) {
break
}
// https://unicode.org/reports/tr29/#GB6
if current.is(_L|_V|_LV|_LVT) && last.is(_L) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB7
if current.is(_V|_T) && last.is(_LV|_V) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB8
if current.is(_T) && last.is(_LVT|_T) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB9
if current.is(_Extend | _ZWJ) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB9a
if current.is(_SpacingMark) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB9b
if last.is(_Prepend) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB9c
// Do not break within certain combinations with Indic_Conjunct_Break (InCB)=Linker.
if incb == incbLinker && current.is(_InCBConsonant) {
// After matching the pattern, reset state to start tracking a new pattern
// The current Consonant becomes the start of the new pattern
incb = incbConsonant
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB11
if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) {
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB12
// https://unicode.org/reports/tr29/#GB13
if (current & last).is(_RegionalIndicator) {
regionalIndicatorCount++
odd := regionalIndicatorCount%2 == 1
if odd {
pos += w
continue
}
}
// If we fall through all the above rules, it's a grapheme cluster break
break
}
// Return token
return pos, data[:pos], nil
}

1717
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go generated vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,24 +0,0 @@
//go:build !windows
// +build !windows
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcell
// NewConsoleScreen returns a console based screen. This platform
// doesn't have support for any, so it returns nil and a suitable error.
func NewConsoleScreen() (Screen, error) {
return nil, ErrNoScreen
}

File diff suppressed because it is too large Load diff

View file

@ -1,47 +0,0 @@
// Copyright 2018 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package tcell provides a lower-level, portable API for building
// programs that interact with terminals or consoles. It works with
// both common (and many uncommon!) terminals or terminal emulators,
// and Windows console implementations.
//
// It provides support for up to 256 colors, text attributes, and box drawing
// elements. A database of terminals built from a real terminfo database
// is provided, along with code to generate new database entries.
//
// Tcell offers very rich support for mice, dependent upon the terminal
// of course. (Windows, XTerm, and iTerm 2 are known to work very well.)
//
// If the environment is not Unicode by default, such as an ISO8859 based
// locale or GB18030, Tcell can convert input and output, so that your
// terminal can operate in whatever locale is most convenient, while the
// application program can just assume "everything is UTF-8". Reasonable
// defaults are used for updating characters to something suitable for
// display. Unicode box drawing characters will be converted to use the
// alternate character set of your terminal, if native conversions are
// not available. If no ACS is available, then some ASCII fallbacks will
// be used.
//
// Note that support for non-UTF-8 locales (other than C) must be enabled
// by the application using RegisterEncoding() -- we don't have them all
// enabled by default to avoid bloating the application unnecessarily.
// (These days UTF-8 is good enough for almost everyone, and nobody should
// be using legacy locales anymore.) Also, actual glyphs for various code
// point will only be displayed if your terminal or emulator (or the font
// the emulator is using) supports them.
//
// A rich set of key codes is supported, with support for up to 65 function
// keys, and various other special keys.
package tcell

View file

@ -1,535 +0,0 @@
// Copyright 2024 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcell
import (
"sync"
"unicode/utf8"
"golang.org/x/text/transform"
)
// NewSimulationScreen returns a SimulationScreen. Note that
// SimulationScreen is also a Screen.
func NewSimulationScreen(charset string) SimulationScreen {
if charset == "" {
charset = "UTF-8"
}
ss := &simscreen{charset: charset}
ss.Screen = &baseScreen{screenImpl: ss}
return ss
}
// SimulationScreen represents a screen simulation. This is intended to
// be a superset of normal Screens, but also adds some important interfaces
// for testing.
type SimulationScreen interface {
Screen
// InjectKeyBytes injects a stream of bytes corresponding to
// the native encoding (see charset). It turns true if the entire
// set of bytes were processed and delivered as KeyEvents, false
// if any bytes were not fully understood. Any bytes that are not
// fully converted are discarded.
InjectKeyBytes(buf []byte) bool
// InjectKey injects a key event. The rune is a UTF-8 rune, post
// any translation.
InjectKey(key Key, r rune, mod ModMask)
// InjectMouse injects a mouse event.
InjectMouse(x, y int, buttons ButtonMask, mod ModMask)
// GetContents returns screen contents as an array of
// cells, along with the physical width & height. Note that the
// physical contents will be used until the next time SetSize()
// is called.
GetContents() (cells []SimCell, width int, height int)
// GetCursor returns the cursor details.
GetCursor() (x int, y int, visible bool)
// GetTitle gets the previously set title.
GetTitle() string
// GetClipboardData gets the actual data for the clipboard.
GetClipboardData() []byte
}
// SimCell represents a simulated screen cell. The purpose of this
// is to track on screen content.
type SimCell struct {
// Bytes is the actual character bytes. Normally this is
// rune data, but it could be be data in another encoding system.
Bytes []byte
// Style is the style used to display the data.
Style Style
// Runes is the list of runes, unadulterated, in UTF-8.
Runes []rune
}
type simscreen struct {
physw int
physh int
fini bool
style Style
evch chan Event
quit chan struct{}
front []SimCell
back CellBuffer
clear bool
cursorx int
cursory int
cursorvis bool
mouse bool
paste bool
charset string
encoder transform.Transformer
decoder transform.Transformer
fillchar rune
fillstyle Style
fallback map[rune]string
title string
clipboard []byte
Screen
sync.Mutex
}
func (s *simscreen) Init() error {
s.evch = make(chan Event, 10)
s.quit = make(chan struct{})
s.fillchar = 'X'
s.fillstyle = StyleDefault
s.mouse = false
s.physw = 80
s.physh = 25
s.cursorx = -1
s.cursory = -1
s.style = StyleDefault
if enc := GetEncoding(s.charset); enc != nil {
s.encoder = enc.NewEncoder()
s.decoder = enc.NewDecoder()
} else {
return ErrNoCharset
}
s.front = make([]SimCell, s.physw*s.physh)
s.back.Resize(80, 25)
// default fallbacks
s.fallback = make(map[rune]string)
for k, v := range RuneFallbacks {
s.fallback[k] = v
}
return nil
}
func (s *simscreen) Fini() {
s.Lock()
s.fini = true
s.back.Resize(0, 0)
s.Unlock()
if s.quit != nil {
close(s.quit)
}
s.physw = 0
s.physh = 0
s.front = nil
}
func (s *simscreen) SetStyle(style Style) {
s.Lock()
s.style = style
s.Unlock()
}
func (s *simscreen) drawCell(x, y int) int {
mainc, combc, style, width := s.back.GetContent(x, y)
if !s.back.Dirty(x, y) {
return width
}
if x >= s.physw || y >= s.physh || x < 0 || y < 0 {
return width
}
simc := &s.front[(y*s.physw)+x]
if style == StyleDefault {
style = s.style
}
simc.Style = style
simc.Runes = append([]rune{mainc}, combc...)
// now emit runes - taking care to not overrun width with a
// wide character, and to ensure that we emit exactly one regular
// character followed up by any residual combing characters
simc.Bytes = nil
if x > s.physw-width {
simc.Runes = []rune{' '}
simc.Bytes = []byte{' '}
return width
}
lbuf := make([]byte, 12)
ubuf := make([]byte, 12)
nout := 0
for _, r := range simc.Runes {
l := utf8.EncodeRune(ubuf, r)
nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true)
if nout == 0 || lbuf[0] == '\x1a' {
// skip combining
if subst, ok := s.fallback[r]; ok {
simc.Bytes = append(simc.Bytes,
[]byte(subst)...)
} else if r >= ' ' && r <= '~' {
simc.Bytes = append(simc.Bytes, byte(r))
} else if simc.Bytes == nil {
simc.Bytes = append(simc.Bytes, '?')
}
} else {
simc.Bytes = append(simc.Bytes, lbuf[:nout]...)
}
}
s.back.SetDirty(x, y, false)
return width
}
func (s *simscreen) ShowCursor(x, y int) {
s.Lock()
s.cursorx, s.cursory = x, y
s.showCursor()
s.Unlock()
}
func (s *simscreen) HideCursor() {
s.ShowCursor(-1, -1)
}
func (s *simscreen) showCursor() {
x, y := s.cursorx, s.cursory
if x < 0 || y < 0 || x >= s.physw || y >= s.physh {
s.cursorvis = false
} else {
s.cursorvis = true
}
}
func (s *simscreen) hideCursor() {
// does not update cursor position
s.cursorvis = false
}
func (s *simscreen) SetCursor(CursorStyle, Color) {}
func (s *simscreen) Show() {
s.Lock()
s.resize()
s.draw()
s.Unlock()
}
func (s *simscreen) clearScreen() {
// We emulate a hardware clear by filling with a specific pattern
for i := range s.front {
s.front[i].Style = s.fillstyle
s.front[i].Runes = []rune{s.fillchar}
s.front[i].Bytes = []byte{byte(s.fillchar)}
}
s.clear = false
}
func (s *simscreen) draw() {
s.hideCursor()
if s.clear {
s.clearScreen()
}
w, h := s.back.Size()
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
width := s.drawCell(x, y)
x += width - 1
}
}
s.showCursor()
}
func (s *simscreen) EnableMouse(...MouseFlags) {
s.mouse = true
}
func (s *simscreen) DisableMouse() {
s.mouse = false
}
func (s *simscreen) EnablePaste() {
s.paste = true
}
func (s *simscreen) DisablePaste() {
s.paste = false
}
func (s *simscreen) EnableFocus() {
}
func (s *simscreen) DisableFocus() {
}
func (s *simscreen) Size() (int, int) {
s.Lock()
w, h := s.back.Size()
s.Unlock()
return w, h
}
func (s *simscreen) resize() {
w, h := s.physw, s.physh
ow, oh := s.back.Size()
if w != ow || h != oh {
s.back.Resize(w, h)
ev := NewEventResize(w, h)
s.postEvent(ev)
}
}
func (s *simscreen) Colors() int {
return 256
}
func (s *simscreen) postEvent(ev Event) {
select {
case s.evch <- ev:
case <-s.quit:
}
}
func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) {
ev := NewEventMouse(x, y, buttons, mod)
s.postEvent(ev)
}
func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) {
ev := NewEventKey(key, r, mod)
s.postEvent(ev)
}
func (s *simscreen) InjectKeyBytes(b []byte) bool {
failed := false
outer:
for len(b) > 0 {
if b[0] >= ' ' && b[0] <= 0x7F {
// printable ASCII easy to deal with -- no encodings
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
s.postEvent(ev)
b = b[1:]
continue
}
if b[0] < 0x80 {
// No encodings start with low numbered values
if b[0] > 0 && b[0] < ' ' { // control keys
switch Key(b[0]) {
case KeyESC, KeyEnter, KeyTAB:
s.postEvent(NewEventKey(Key(b[0]), 0, 0))
continue;
default:
s.postEvent(NewEventKey(Key(b[0]), rune(b[0])+'\x60', ModCtrl))
continue
}
}
mod := ModNone
ev := NewEventKey(Key(b[0]), 0, mod)
s.postEvent(ev)
b = b[1:]
continue
}
utfb := make([]byte, len(b)*4) // worst case
for l := 1; l < len(b); l++ {
s.decoder.Reset()
nout, nin, _ := s.decoder.Transform(utfb, b[:l], true)
if nout != 0 {
r, _ := utf8.DecodeRune(utfb[:nout])
if r != utf8.RuneError {
ev := NewEventKey(KeyRune, r, ModNone)
s.postEvent(ev)
}
b = b[nin:]
continue outer
}
}
failed = true
b = b[1:]
continue
}
return !failed
}
func (s *simscreen) Sync() {
s.Lock()
s.clear = true
s.resize()
s.back.Invalidate()
s.draw()
s.Unlock()
}
func (s *simscreen) CharacterSet() string {
return s.charset
}
func (s *simscreen) SetSize(w, h int) {
s.Lock()
newc := make([]SimCell, w*h)
for row := 0; row < h && row < s.physh; row++ {
for col := 0; col < w && col < s.physw; col++ {
newc[(row*w)+col] = s.front[(row*s.physw)+col]
}
}
s.cursorx, s.cursory = -1, -1
s.physw, s.physh = w, h
s.front = newc
s.back.Resize(w, h)
s.Unlock()
}
func (s *simscreen) GetContents() ([]SimCell, int, int) {
s.Lock()
cells, w, h := s.front, s.physw, s.physh
s.Unlock()
return cells, w, h
}
func (s *simscreen) GetCursor() (int, int, bool) {
s.Lock()
x, y, vis := s.cursorx, s.cursory, s.cursorvis
s.Unlock()
return x, y, vis
}
func (s *simscreen) RegisterRuneFallback(r rune, subst string) {
s.Lock()
s.fallback[r] = subst
s.Unlock()
}
func (s *simscreen) UnregisterRuneFallback(r rune) {
s.Lock()
delete(s.fallback, r)
s.Unlock()
}
func (s *simscreen) CanDisplay(r rune, checkFallbacks bool) bool {
if enc := s.encoder; enc != nil {
nb := make([]byte, 6)
ob := make([]byte, 6)
num := utf8.EncodeRune(ob, r)
enc.Reset()
dst, _, err := enc.Transform(nb, ob[:num], true)
if dst != 0 && err == nil && nb[0] != '\x1A' {
return true
}
}
if !checkFallbacks {
return false
}
if _, ok := s.fallback[r]; ok {
return true
}
return false
}
func (s *simscreen) HasMouse() bool {
return false
}
func (s *simscreen) Resize(int, int, int, int) {}
func (s *simscreen) HasKey(Key) bool {
return true
}
func (s *simscreen) Beep() error {
return nil
}
func (s *simscreen) Suspend() error {
return nil
}
func (s *simscreen) Resume() error {
return nil
}
func (s *simscreen) Tty() (Tty, bool) {
return nil, false
}
func (s *simscreen) GetCells() *CellBuffer {
return &s.back
}
func (s *simscreen) EventQ() chan Event {
return s.evch
}
func (s *simscreen) StopQ() <-chan struct{} {
return s.quit
}
func (s *simscreen) SetTitle(title string) {
s.title = title
}
func (s *simscreen) GetTitle() string {
return s.title
}
func (s *simscreen) SetClipboard(data []byte) {
s.clipboard = data
}
func (s *simscreen) GetClipboard() {
if s.clipboard != nil {
ev := NewEventClipboard(s.clipboard)
s.postEvent(ev)
}
}
func (s *simscreen) GetClipboardData() []byte {
return s.clipboard
}

View file

@ -1 +0,0 @@
mkinfo

View file

@ -1,25 +0,0 @@
This package represents the parent for all terminals.
In older versions of tcell we had (a couple of) different
external file formats for the terminal database. Those are
now removed. All terminal definitions are supplied by
one of two methods:
1. Compiled Go code
2. For systems with terminfo and infocmp, dynamically
generated at runtime.
The Go code can be generated using the mkinfo utility in
this directory. The database entry should be generated
into a package in a directory named as the first character
of the package name. (This permits us to group them all
without having a huge directory of little packages.)
It may be desirable to add new packages to the extended
package, or -- rarely -- the base package.
Applications which want to have the large set of terminal
descriptions built into the binary can simply import the
extended package. Otherwise a smaller reasonable default
set (the base package) will be included instead.

View file

@ -1,7 +0,0 @@
TERMINALS
=========
The best way to populate terminals on Debian is to install ncurses,
ncurses-term, screen, tmux, rxvt-unicode, and dvtm. This populates the
the terminfo database so that we can have a reasonable set of starting
terminals.

View file

@ -1,31 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package aixterm
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// IBM Aixterm Terminal Emulator
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "aixterm",
Columns: 80,
Lines: 25,
Colors: 8,
Clear: "\x1b[H\x1b[J",
AttrOff: "\x1b[0;10m\x1b(B",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[32m\x1b[40m",
PadChar: "\x00",
AltChars: "jjkkllmmnnqqttuuvvwwxx",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,41 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package alacritty
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// alacritty with direct color indexing
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "alacritty-direct",
Columns: 80,
Lines: 24,
Colors: 16777216,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b(B\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
TrueColor: true,
AutoMargin: true,
})
}

View file

@ -1,44 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package alacritty
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// alacritty terminal emulator
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "alacritty",
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b(B\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[<",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
}

View file

@ -1,32 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package ansi
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// ansi/pc-term compatible with color
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "ansi",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[J",
AttrOff: "\x1b[0;10m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
EnterAcs: "\x1b[11m",
ExitAcs: "\x1b[10m",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,33 +0,0 @@
// Copyright 2020 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This is just a "minimalist" set of the base terminal descriptions.
// It should be sufficient for most applications.
// Package base contains the base terminal descriptions that are likely
// to be needed by any stock application. It is imported by default in the
// terminfo package, so terminal types listed here will be available to any
// tcell application.
package base
import (
// The following imports just register themselves --
// these are the terminal types we aggregate in this package.
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
_ "github.com/gdamore/tcell/v2/terminfo/t/tmux"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt100"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
)

View file

@ -1,32 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package cygwin
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// ANSI emulation for Cygwin
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "cygwin",
Colors: 8,
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
AttrOff: "\x1b[0;10m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
EnterAcs: "\x1b[11m",
ExitAcs: "\x1b[10m",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
InsertChar: "\x1b[@",
})
}

View file

@ -1,38 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package dtterm
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// CDE desktop terminal
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "dtterm",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[J",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,266 +0,0 @@
// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The dynamic package is used to generate a terminal description dynamically,
// using infocmp. This is really a method of last resort, as the performance
// will be slow, and it requires a working infocmp. But, the hope is that it
// will assist folks who have to deal with a terminal description that isn't
// already built in. This requires infocmp to be in the user's path, and to
// support reasonably the -1 option.
package dynamic
import (
"bytes"
"errors"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"github.com/gdamore/tcell/v2/terminfo"
)
type termcap struct {
name string
desc string
aliases []string
bools map[string]bool
nums map[string]int
strs map[string]string
}
func (tc *termcap) getnum(s string) int {
return (tc.nums[s])
}
func (tc *termcap) getflag(s string) bool {
return (tc.bools[s])
}
func (tc *termcap) getstr(s string) string {
return (tc.strs[s])
}
const (
none = iota
control
escaped
)
var errNotAddressable = errors.New("terminal not cursor addressable")
func unescape(s string) string {
// Various escapes are in \x format. Control codes are
// encoded as ^M (carat followed by ASCII equivalent).
// escapes are: \e, \E - escape
// \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
buf := &bytes.Buffer{}
esc := none
for i := 0; i < len(s); i++ {
c := s[i]
switch esc {
case none:
switch c {
case '\\':
esc = escaped
case '^':
esc = control
default:
buf.WriteByte(c)
}
case control:
buf.WriteByte(c ^ 1<<6)
esc = none
case escaped:
switch c {
case 'E', 'e':
buf.WriteByte(0x1b)
case '0', '1', '2', '3', '4', '5', '6', '7':
if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' {
buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0'))
i = i + 2
} else if c == '0' {
buf.WriteByte(0)
}
case 'n':
buf.WriteByte('\n')
case 'r':
buf.WriteByte('\r')
case 't':
buf.WriteByte('\t')
case 'b':
buf.WriteByte('\b')
case 'f':
buf.WriteByte('\f')
case 's':
buf.WriteByte(' ')
default:
buf.WriteByte(c)
}
esc = none
}
}
return (buf.String())
}
func (tc *termcap) setupterm(name string) error {
cmd := exec.Command("infocmp", "-1", name)
output := &bytes.Buffer{}
cmd.Stdout = output
tc.strs = make(map[string]string)
tc.bools = make(map[string]bool)
tc.nums = make(map[string]int)
if err := cmd.Run(); err != nil {
return fmt.Errorf("couldn't open terminfo ($TERM) file for %s: %w", name, err)
}
// Now parse the output.
// We get comment lines (starting with "#"), followed by
// a header line that looks like "<name>|<alias>|...|<desc>"
// then capabilities, one per line, starting with a tab and ending
// with a comma and newline.
lines := strings.Split(output.String(), "\n")
for len(lines) > 0 && strings.HasPrefix(lines[0], "#") {
lines = lines[1:]
}
// Ditch trailing empty last line
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
header := lines[0]
header = strings.TrimSuffix(header, ",")
names := strings.Split(header, "|")
tc.name = names[0]
names = names[1:]
if len(names) > 0 {
tc.desc = names[len(names)-1]
names = names[:len(names)-1]
}
tc.aliases = names
for _, val := range lines[1:] {
if (!strings.HasPrefix(val, "\t")) ||
(!strings.HasSuffix(val, ",")) {
return (errors.New("malformed infocmp: " + val))
}
val = val[1:]
val = val[:len(val)-1]
if k := strings.SplitN(val, "=", 2); len(k) == 2 {
tc.strs[k[0]] = unescape(k[1])
} else if k := strings.SplitN(val, "#", 2); len(k) == 2 {
u, err := strconv.ParseUint(k[1], 0, 0)
if err != nil {
return (err)
}
tc.nums[k[0]] = int(u)
} else {
tc.bools[val] = true
}
}
return nil
}
// LoadTerminfo creates a Terminfo by for named terminal by attempting to parse
// the output from infocmp. This returns the terminfo entry, a description of
// the terminal, and either nil or an error.
func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
var tc termcap
if err := tc.setupterm(name); err != nil {
return nil, "", err
}
t := &terminfo.Terminfo{}
t.Name = tc.name
t.Aliases = tc.aliases
t.Colors = tc.getnum("colors")
t.Columns = tc.getnum("cols")
t.Lines = tc.getnum("lines")
t.Clear = tc.getstr("clear")
t.EnterCA = tc.getstr("smcup")
t.ExitCA = tc.getstr("rmcup")
t.ShowCursor = tc.getstr("cnorm")
t.HideCursor = tc.getstr("civis")
t.AttrOff = tc.getstr("sgr0")
t.Underline = tc.getstr("smul")
t.Bold = tc.getstr("bold")
t.Blink = tc.getstr("blink")
t.Dim = tc.getstr("dim")
t.Italic = tc.getstr("sitm")
t.Reverse = tc.getstr("rev")
t.EnterKeypad = tc.getstr("smkx")
t.ExitKeypad = tc.getstr("rmkx")
t.SetFg = tc.getstr("setaf")
t.SetBg = tc.getstr("setab")
t.SetCursor = tc.getstr("cup")
t.AltChars = tc.getstr("acsc")
t.EnterAcs = tc.getstr("smacs")
t.ExitAcs = tc.getstr("rmacs")
t.EnableAcs = tc.getstr("enacs")
t.Mouse = tc.getstr("kmous")
// Technically the RGB flag that is provided for xterm-direct is not
// quite right. The problem is that the -direct flag that was introduced
// with ncurses 6.1 requires a parsing for the parameters that we lack.
// For this case we'll just assume it's XTerm compatible. Someday this
// may be incorrect, but right now it is correct, and nobody uses it
// anyway.
if tc.getflag("Tc") {
// This presumes XTerm 24-bit true color.
t.TrueColor = true
} else if tc.getflag("RGB") {
// This is for xterm-direct, which uses a different scheme entirely.
// (ncurses went a very different direction from everyone else, and
// so it's unlikely anything is using this definition.)
t.TrueColor = true
t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
}
// We only support colors in ANSI 8 or 256 color mode.
if t.Colors < 8 || t.SetFg == "" {
t.Colors = 0
}
if t.SetCursor == "" {
return nil, "", errNotAddressable
}
// For padding, we lookup the pad char. If that isn't present,
// and npc is *not* set, then we assume a null byte.
t.PadChar = tc.getstr("pad")
if t.PadChar == "" {
if !tc.getflag("npc") {
t.PadChar = "\u0000"
}
}
// For terminals that use "standard" SGR sequences, lets combine the
// foreground and background together.
if strings.HasPrefix(t.SetFg, "\x1b[") &&
strings.HasPrefix(t.SetBg, "\x1b[") &&
strings.HasSuffix(t.SetFg, "m") &&
strings.HasSuffix(t.SetBg, "m") {
fg := t.SetFg[:len(t.SetFg)-1]
r := regexp.MustCompile("%p1")
bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
t.SetFgBg = fg + ";" + bg
}
return t, tc.desc, nil
}

View file

@ -1,48 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package emacs
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// GNU Emacs term.el terminal emulation
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "eterm",
Columns: 80,
Lines: 24,
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
AttrOff: "\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Reverse: "\x1b[7m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
// Emacs term.el terminal emulator term-protocol-version 0.96
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "eterm-color",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
AttrOff: "\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
SetFg: "\x1b[%p1%{30}%+%dm",
SetBg: "\x1b[%p1%'('%+%dm",
SetFgBg: "\x1b[%p1%{30}%+%d;%p2%'('%+%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,51 +0,0 @@
// Copyright 2024 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package extended contains an extended set of terminal descriptions.
// Applications desiring to have a better chance of Just Working by
// default should include this package. This will significantly increase
// the size of the program.
package extended
import (
// The following imports just register themselves --
// these are the terminal types we aggregate in this package.
_ "github.com/gdamore/tcell/v2/terminfo/a/aixterm"
_ "github.com/gdamore/tcell/v2/terminfo/a/alacritty"
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
_ "github.com/gdamore/tcell/v2/terminfo/c/cygwin"
_ "github.com/gdamore/tcell/v2/terminfo/d/dtterm"
_ "github.com/gdamore/tcell/v2/terminfo/e/emacs"
_ "github.com/gdamore/tcell/v2/terminfo/f/foot"
_ "github.com/gdamore/tcell/v2/terminfo/g/gnome"
_ "github.com/gdamore/tcell/v2/terminfo/k/konsole"
_ "github.com/gdamore/tcell/v2/terminfo/k/kterm"
_ "github.com/gdamore/tcell/v2/terminfo/l/linux"
_ "github.com/gdamore/tcell/v2/terminfo/p/pcansi"
_ "github.com/gdamore/tcell/v2/terminfo/r/rxvt"
_ "github.com/gdamore/tcell/v2/terminfo/s/screen"
_ "github.com/gdamore/tcell/v2/terminfo/s/simpleterm"
_ "github.com/gdamore/tcell/v2/terminfo/s/sun"
_ "github.com/gdamore/tcell/v2/terminfo/t/tmux"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt100"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt320"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt400"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt420"
_ "github.com/gdamore/tcell/v2/terminfo/x/xfce"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty"
)

View file

@ -1,42 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package foot
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// foot terminal emulator
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "foot",
Aliases: []string{"foot-extra"},
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b(B\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48:5:%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48:5:%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,80 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package gnome
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// GNOME Terminal
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "gnome",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[0m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b)0",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
// GNOME Terminal with xterm 256-colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "gnome-256color",
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[0m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b)0",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
}

View file

@ -1,19 +0,0 @@
#!/bin/bash
while read line
do
case "$line" in
*'|'*)
alias=${line#*|}
line=${line%|*}
;;
*)
alias=${line%%,*}
;;
esac
alias=${alias//-/_}
direc=${alias:0:1}
mkdir -p ${direc}/${alias}
go run mkinfo.go -P ${alias} -go ${direc}/${alias}/term.go ${line//,/ }
done < models.txt

View file

@ -1,82 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package konsole
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// KDE console window
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "konsole",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[0m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b)0",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[<",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
// KDE console window with xterm 256-colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "konsole-256color",
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[0m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b)0",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[<",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
}

View file

@ -1,39 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package kterm
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// kterm kanji terminal emulator (X window system)
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "kterm",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
AttrOff: "\x1b[m\x1b(B",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aajjkkllmmnnooppqqrrssttuuvvwwxx~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
}

View file

@ -1,38 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package linux
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// Linux console
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "linux",
Colors: 8,
Clear: "\x1b[H\x1b[J",
ShowCursor: "\x1b[?25h\x1b[?0c",
HideCursor: "\x1b[?25l\x1b[?1c",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b)0",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
InsertChar: "\x1b[@",
})
}

View file

@ -1,29 +0,0 @@
aixterm
alacritty
ansi
cygwin
dtterm
eterm,eterm-color|emacs
gnome,gnome-256color
hpterm
konsole,konsole-256color
kterm
linux
pcansi
rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color
screen,screen-256color
st,st-256color|simpleterm
tmux,tmux-256color
vt100
vt102
vt220
vt320
vt400
vt420
wy50
wy60
wy99-ansi,wy99a-ansi
xfce
xterm,xterm-88color,xterm-256color
xterm-ghostty
xterm-kitty

View file

@ -1,32 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package pcansi
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// ibm-pc terminal programs claiming to be ANSI
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "pcansi",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[J",
AttrOff: "\x1b[0;10m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[37;40m",
PadChar: "\x00",
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
EnterAcs: "\x1b[12m",
ExitAcs: "\x1b[10m",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,176 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package rxvt
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// rxvt terminal emulator (X Window System)
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "rxvt",
Aliases: []string{"rxvt-color"},
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b=",
ExitKeypad: "\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
// rxvt 2.7.9 with xterm 256-colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "rxvt-256color",
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b=",
ExitKeypad: "\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
// rxvt 2.7.9 with xterm 88-colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "rxvt-88color",
Columns: 80,
Lines: 24,
Colors: 88,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b7\x1b[?47h",
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b=",
ExitKeypad: "\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
// rxvt-unicode terminal (X Window System)
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "rxvt-unicode",
Columns: 80,
Lines: 24,
Colors: 88,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[r\x1b[?1049l",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x1b(B",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b=",
ExitKeypad: "\x1b>",
SetFg: "\x1b[38;5;%p1%dm",
SetBg: "\x1b[48;5;%p1%dm",
SetFgBg: "\x1b[38;5;%p1%d;48;5;%p2%dm",
ResetFgBg: "\x1b[39;49m",
AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
InsertChar: "\x1b[@",
})
// rxvt-unicode terminal with 256 colors (X Window System)
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "rxvt-unicode-256color",
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[r\x1b[?1049l",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x1b(B",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b=",
ExitKeypad: "\x1b>",
SetFg: "\x1b[38;5;%p1%dm",
SetBg: "\x1b[48;5;%p1%dm",
SetFgBg: "\x1b[38;5;%p1%d;48;5;%p2%dm",
ResetFgBg: "\x1b[39;49m",
AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
InsertChar: "\x1b[@",
})
}

View file

@ -1,74 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package screen
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// VT 100/ANSI X3.64 virtual terminal
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "screen",
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[34h\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
// GNU Screen with 256 colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "screen-256color",
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[34h\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
})
}

View file

@ -1,80 +0,0 @@
// Generated automatically. DO NOT HAND-EDIT.
package simpleterm
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// aka simpleterm
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "st",
Aliases: []string{"stterm"},
Columns: 80,
Lines: 24,
Colors: 8,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[0m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAcs: "\x1b)0",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
// simpleterm with 256 colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "st-256color",
Aliases: []string{"stterm-256color"},
Columns: 80,
Lines: 24,
Colors: 256,
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[0m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAcs: "\x1b)0",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
XTermLike: true,
})
}

View file

@ -1,60 +0,0 @@
// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This terminal definition is hand-coded, as the default terminfo for
// this terminal is busted with respect to color. Unlike pretty much every
// other ANSI compliant terminal, this terminal cannot combine foreground and
// background escapes. The default terminfo also only provides escapes for
// 16-bit color.
package sun
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// Sun Microsystems Inc. workstation console
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "sun",
Aliases: []string{"sun1", "sun2"},
Columns: 80,
Lines: 34,
Clear: "\f",
AttrOff: "\x1b[m",
Reverse: "\x1b[7m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
InsertChar: "\x1b[@",
})
// Sun Microsystems Workstation console with color support (IA systems)
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "sun-color",
Columns: 80,
Lines: 34,
Colors: 256,
Clear: "\f",
AttrOff: "\x1b[m",
Bold: "\x1b[1m",
Reverse: "\x1b[7m",
SetFg: "\x1b[38;5;%p1%dm",
SetBg: "\x1b[48;5;%p1%dm",
ResetFgBg: "\x1b[0m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
AutoMargin: true,
InsertChar: "\x1b[@",
})
}

Some files were not shown because too many files have changed in this diff Show more