mirror of
https://github.com/peco/peco.git
synced 2026-09-10 07:16:29 -04:00
Merge pull request #622 from peco/rename-termbox
Replace Termbox (name references) with Tcell
This commit is contained in:
commit
550d56c9a4
40
README.md
40
README.md
|
|
@ -806,34 +806,34 @@ For now, styles of following 7 items can be customized in `config.json`.
|
|||
|
||||
### Foreground Colors
|
||||
|
||||
- `"black"` for `termbox.ColorBlack`
|
||||
- `"red"` for `termbox.ColorRed`
|
||||
- `"green"` for `termbox.ColorGreen`
|
||||
- `"yellow"` for `termbox.ColorYellow`
|
||||
- `"blue"` for `termbox.ColorBlue`
|
||||
- `"magenta"` for `termbox.ColorMagenta`
|
||||
- `"cyan"` for `termbox.ColorCyan`
|
||||
- `"white"` for `termbox.ColorWhite`
|
||||
- `"black"` for `tcell.ColorBlack`
|
||||
- `"red"` for `tcell.ColorRed`
|
||||
- `"green"` for `tcell.ColorGreen`
|
||||
- `"yellow"` for `tcell.ColorYellow`
|
||||
- `"blue"` for `tcell.ColorBlue`
|
||||
- `"magenta"` for `tcell.ColorMagenta`
|
||||
- `"cyan"` for `tcell.ColorCyan`
|
||||
- `"white"` for `tcell.ColorWhite`
|
||||
- `"0"`-`"255"` for 256color ([Use256Color](#use256color) must be enabled)
|
||||
|
||||
### Background Colors
|
||||
|
||||
- `"on_black"` for `termbox.ColorBlack`
|
||||
- `"on_red"` for `termbox.ColorRed`
|
||||
- `"on_green"` for `termbox.ColorGreen`
|
||||
- `"on_yellow"` for `termbox.ColorYellow`
|
||||
- `"on_blue"` for `termbox.ColorBlue`
|
||||
- `"on_magenta"` for `termbox.ColorMagenta`
|
||||
- `"on_cyan"` for `termbox.ColorCyan`
|
||||
- `"on_white"` for `termbox.ColorWhite`
|
||||
- `"on_black"` for `tcell.ColorBlack`
|
||||
- `"on_red"` for `tcell.ColorRed`
|
||||
- `"on_green"` for `tcell.ColorGreen`
|
||||
- `"on_yellow"` for `tcell.ColorYellow`
|
||||
- `"on_blue"` for `tcell.ColorBlue`
|
||||
- `"on_magenta"` for `tcell.ColorMagenta`
|
||||
- `"on_cyan"` for `tcell.ColorCyan`
|
||||
- `"on_white"` for `tcell.ColorWhite`
|
||||
- `"on_0"`-`"on_255"` for 256color ([Use256Color](#use256color) must be enabled)
|
||||
|
||||
### Attributes
|
||||
|
||||
- `"bold"` for fg: `termbox.AttrBold`
|
||||
- `"underline"` for fg: `termbox.AttrUnderline`
|
||||
- `"reverse"` for fg: `termbox.AttrReverse`
|
||||
- `"on_bold"` for bg: `termbox.AttrBold` (this attribute actually makes the background blink on some platforms/environments, e.g. linux console, xterm...)
|
||||
- `"bold"` for fg: `tcell.AttrBold`
|
||||
- `"underline"` for fg: `tcell.AttrUnderline`
|
||||
- `"reverse"` for fg: `tcell.AttrReverse`
|
||||
- `"on_bold"` for bg: `tcell.AttrBold` (this attribute actually makes the background blink on some platforms/environments, e.g. linux console, xterm...)
|
||||
|
||||
## CustomFilter
|
||||
|
||||
|
|
|
|||
4
event.go
4
event.go
|
|
@ -14,9 +14,7 @@ const (
|
|||
EventError
|
||||
)
|
||||
|
||||
// Event is peco's internal event type, replacing termbox.Event.
|
||||
// This decouples all code outside the screen adapter from the
|
||||
// terminal library.
|
||||
// Event is peco's internal event type, decoupled from any terminal library.
|
||||
type Event struct {
|
||||
Type EventType
|
||||
Key keyseq.KeyType
|
||||
|
|
|
|||
2
input.go
2
input.go
|
|
@ -49,7 +49,7 @@ func (i *Input) handleInputEvent(ctx context.Context, ev Event) error {
|
|||
return nil
|
||||
case EventKey:
|
||||
// ModAlt is a sequence of letters with a leading \x1b (=Esc).
|
||||
// It would be nice if termbox differentiated this for us, but
|
||||
// The terminal library doesn't differentiate this for us, so
|
||||
// we workaround it by waiting (juuuust a few milliseconds) for
|
||||
// extra key events. If no extra events arrive, it should be Esc
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ import (
|
|||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// KeyType represents a keyboard key. Values are defined to match termbox-go's
|
||||
// constants so that the adapter layer in screen.go can do simple type casts
|
||||
// during Phase 1 of the migration. In Phase 2, the adapter will map between
|
||||
// peco's KeyType and tcell's key constants.
|
||||
// KeyType represents a keyboard key. The adapter layer in screen.go maps
|
||||
// between peco's KeyType and tcell's key constants.
|
||||
type KeyType uint16
|
||||
|
||||
// Function keys
|
||||
|
|
@ -39,8 +37,8 @@ const (
|
|||
)
|
||||
|
||||
// Mouse keys.
|
||||
// In termbox-go there is one internal gap constant between KeyArrowRight
|
||||
// and MouseLeft, so MouseLeft = KeyArrowRight - 2 (not -1).
|
||||
// There is an intentional gap between KeyArrowRight and MouseLeft
|
||||
// (MouseLeft = KeyArrowRight - 2, not -1) for historical reasons.
|
||||
const (
|
||||
MouseLeft KeyType = 0xFFFF - iota - 23 // KeyArrowRight - 2
|
||||
MouseMiddle // KeyArrowRight - 3
|
||||
|
|
@ -211,8 +209,7 @@ func ToKeyList(ksk string) (KeyList, error) {
|
|||
}
|
||||
|
||||
// KeyEventToString returns a human-readable name for a key event described
|
||||
// by the given key type, character, and modifier. This replaces the old
|
||||
// EventToString that took a termbox.Event directly.
|
||||
// by the given key type, character, and modifier.
|
||||
func KeyEventToString(key KeyType, ch rune, mod ModifierKey) (string, error) {
|
||||
s := ""
|
||||
if key == 0 {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ func (u UserPrompt) Draw(state *Peco) {
|
|||
fg := u.styles.Query.fg
|
||||
bg := u.styles.Query.bg
|
||||
|
||||
// Used to notify termbox where our cursor is
|
||||
// Used to notify the screen where our cursor is
|
||||
var posX int
|
||||
|
||||
switch ql {
|
||||
|
|
|
|||
4
peco.go
4
peco.go
|
|
@ -118,7 +118,7 @@ func New() *Peco {
|
|||
idgen: newIDGen(),
|
||||
queryExecDelay: 50 * time.Millisecond,
|
||||
readyCh: make(chan struct{}),
|
||||
screen: NewTermbox(),
|
||||
screen: NewTcellScreen(),
|
||||
selection: NewSelection(),
|
||||
maxScanBufferSize: bufio.MaxScanTokenSize,
|
||||
}
|
||||
|
|
@ -425,7 +425,7 @@ func (p *Peco) Run(ctx context.Context) (err error) {
|
|||
}
|
||||
p.source = src
|
||||
|
||||
// If --height is specified, use InlineScreen instead of the default Termbox screen
|
||||
// If --height is specified, use InlineScreen instead of the default TcellScreen
|
||||
if p.heightSpec != nil {
|
||||
p.screen = NewInlineScreen(*p.heightSpec)
|
||||
}
|
||||
|
|
|
|||
39
screen.go
39
screen.go
|
|
@ -13,9 +13,8 @@ import (
|
|||
"github.com/peco/peco/internal/keyseq"
|
||||
)
|
||||
|
||||
// Termbox implements the Screen interface using tcell/v2.
|
||||
// The name is kept for compatibility with the rest of the codebase.
|
||||
type Termbox struct {
|
||||
// TcellScreen implements the Screen interface using tcell/v2.
|
||||
type TcellScreen struct {
|
||||
mutex sync.Mutex
|
||||
screen tcell.Screen
|
||||
resumeCh chan chan struct{}
|
||||
|
|
@ -76,7 +75,7 @@ func tcellEventToEvent(tev tcell.Event) Event {
|
|||
if key == tcell.KeyRune {
|
||||
r := ev.Rune()
|
||||
// Special case: space must be sent as KeySpace with Ch=0
|
||||
// to match termbox behavior expected by doAcceptChar
|
||||
// to match the convention expected by doAcceptChar
|
||||
if r == ' ' {
|
||||
return Event{
|
||||
Type: EventKey,
|
||||
|
|
@ -159,7 +158,7 @@ func attributeToTcellStyle(fg, bg Attribute) tcell.Style {
|
|||
return style
|
||||
}
|
||||
|
||||
func (t *Termbox) Init(cfg *Config) error {
|
||||
func (t *TcellScreen) Init(cfg *Config) error {
|
||||
screen, err := tcell.NewScreen()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create tcell screen: %w", err)
|
||||
|
|
@ -173,8 +172,8 @@ func (t *Termbox) Init(cfg *Config) error {
|
|||
return t.PostInit(cfg)
|
||||
}
|
||||
|
||||
func NewTermbox() *Termbox {
|
||||
return &Termbox{
|
||||
func NewTcellScreen() *TcellScreen {
|
||||
return &TcellScreen{
|
||||
suspendCh: make(chan struct{}),
|
||||
resumeCh: make(chan chan struct{}),
|
||||
doneCh: make(chan struct{}),
|
||||
|
|
@ -184,7 +183,7 @@ func NewTermbox() *Termbox {
|
|||
// finiScreen finalizes the tcell screen without signaling a permanent
|
||||
// shutdown. Used by the suspend handler so the goroutine continues
|
||||
// to listen for further suspend/resume cycles.
|
||||
func (t *Termbox) finiScreen() {
|
||||
func (t *TcellScreen) finiScreen() {
|
||||
t.mutex.Lock()
|
||||
s := t.screen
|
||||
t.screen = nil
|
||||
|
|
@ -197,16 +196,16 @@ func (t *Termbox) finiScreen() {
|
|||
|
||||
// Close permanently shuts down the screen and signals all goroutines
|
||||
// started by PollEvent to exit.
|
||||
func (t *Termbox) Close() error {
|
||||
func (t *TcellScreen) Close() error {
|
||||
if pdebug.Enabled {
|
||||
pdebug.Printf("Termbox: Close")
|
||||
pdebug.Printf("TcellScreen: Close")
|
||||
}
|
||||
t.finiScreen()
|
||||
t.closeOnce.Do(func() { close(t.doneCh) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Termbox) SetCursor(x, y int) {
|
||||
func (t *TcellScreen) SetCursor(x, y int) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
if t.screen == nil {
|
||||
|
|
@ -218,12 +217,12 @@ func (t *Termbox) SetCursor(x, y int) {
|
|||
// SendEvent is used to allow programmers generate random
|
||||
// events, but it's only useful for testing purposes.
|
||||
// When interacting with tcell, this method is a noop
|
||||
func (t *Termbox) SendEvent(_ Event) {
|
||||
func (t *TcellScreen) SendEvent(_ Event) {
|
||||
// no op
|
||||
}
|
||||
|
||||
// Flush calls tcell's Show to synchronize the screen
|
||||
func (t *Termbox) Flush() error {
|
||||
func (t *TcellScreen) Flush() error {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
if t.screen == nil {
|
||||
|
|
@ -236,7 +235,7 @@ func (t *Termbox) Flush() error {
|
|||
// Sync forces a complete redraw of every cell on the physical display.
|
||||
// This recovers from screen corruption caused by external output (e.g.,
|
||||
// STDERR messages written directly to the terminal).
|
||||
func (t *Termbox) Sync() {
|
||||
func (t *TcellScreen) Sync() {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
if t.screen == nil {
|
||||
|
|
@ -248,7 +247,7 @@ func (t *Termbox) Sync() {
|
|||
// PollEvent returns a channel that you can listen to for
|
||||
// terminal events. The actual polling is done in a
|
||||
// separate goroutine
|
||||
func (t *Termbox) PollEvent(ctx context.Context, cfg *Config) chan Event {
|
||||
func (t *TcellScreen) PollEvent(ctx context.Context, cfg *Config) chan Event {
|
||||
evCh := make(chan Event)
|
||||
|
||||
go func() {
|
||||
|
|
@ -313,14 +312,14 @@ func (t *Termbox) PollEvent(ctx context.Context, cfg *Config) chan Event {
|
|||
return evCh
|
||||
}
|
||||
|
||||
func (t *Termbox) Suspend() {
|
||||
func (t *TcellScreen) Suspend() {
|
||||
select {
|
||||
case t.suspendCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Termbox) Resume(ctx context.Context) {
|
||||
func (t *TcellScreen) Resume(ctx context.Context) {
|
||||
// Resume must be a block operation, because we can't safely proceed
|
||||
// without actually knowing that the screen has been re-initialized.
|
||||
// So we send a channel where we expect a reply back, and wait for that.
|
||||
|
|
@ -343,7 +342,7 @@ func (t *Termbox) Resume(ctx context.Context) {
|
|||
}
|
||||
|
||||
// SetCell writes to the terminal
|
||||
func (t *Termbox) SetCell(x, y int, ch rune, fg, bg Attribute) {
|
||||
func (t *TcellScreen) SetCell(x, y int, ch rune, fg, bg Attribute) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
if t.screen == nil {
|
||||
|
|
@ -354,7 +353,7 @@ func (t *Termbox) SetCell(x, y int, ch rune, fg, bg Attribute) {
|
|||
}
|
||||
|
||||
// Size returns the dimensions of the current terminal
|
||||
func (t *Termbox) Size() (int, int) {
|
||||
func (t *TcellScreen) Size() (int, int) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
if t.screen == nil {
|
||||
|
|
@ -374,7 +373,7 @@ type PrintArgs struct {
|
|||
ANSIAttrs []ansi.AttrSpan // per-character ANSI attributes for this segment
|
||||
}
|
||||
|
||||
func (t *Termbox) Print(args PrintArgs) int {
|
||||
func (t *TcellScreen) Print(args PrintArgs) int {
|
||||
return screenPrint(t, args)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ func (s *InlineScreen) PollEvent(ctx context.Context, cfg *Config) chan Event {
|
|||
return evCh
|
||||
}
|
||||
|
||||
// SendEvent is a no-op for InlineScreen (same as Termbox).
|
||||
// SendEvent is a no-op for InlineScreen (same as TcellScreen).
|
||||
func (s *InlineScreen) SendEvent(_ Event) {}
|
||||
|
||||
// Suspend is a no-op for inline mode.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ package peco
|
|||
|
||||
// PostInit is a no-op on POSIX systems. tcell auto-detects
|
||||
// color capability via terminfo.
|
||||
func (t *Termbox) PostInit(cfg *Config) error {
|
||||
func (t *TcellScreen) PostInit(cfg *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestTermboxSuspendHandlerExitsOnClose verifies that the suspend handler
|
||||
// TestTcellScreenSuspendHandlerExitsOnClose verifies that the suspend handler
|
||||
// goroutine (started by PollEvent) exits when Close() is called, even if
|
||||
// the context has not been cancelled. This is the goroutine leak described
|
||||
// in CODE_REVIEW.md §7.1.
|
||||
func TestTermboxSuspendHandlerExitsOnClose(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenSuspendHandlerExitsOnClose(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
// Use a context that will NOT be cancelled during this test.
|
||||
// The goroutine must exit via doneCh, not ctx.Done().
|
||||
|
|
@ -46,11 +46,11 @@ func TestTermboxSuspendHandlerExitsOnClose(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestTermboxPollingGoroutineExitsOnClose verifies that a goroutine blocked
|
||||
// TestTcellScreenPollingGoroutineExitsOnClose verifies that a goroutine blocked
|
||||
// on resumeCh (as the polling goroutine would be after screen finalization)
|
||||
// exits when Close() is called.
|
||||
func TestTermboxPollingGoroutineExitsOnClose(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenPollingGoroutineExitsOnClose(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -78,11 +78,11 @@ func TestTermboxPollingGoroutineExitsOnClose(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestTermboxCloseIdempotent verifies that Close() can be called multiple
|
||||
// TestTcellScreenCloseIdempotent verifies that Close() can be called multiple
|
||||
// times without panicking (important because the suspend handler calls
|
||||
// finiScreen and then Close() is called at shutdown).
|
||||
func TestTermboxCloseIdempotent(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenCloseIdempotent(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
tb.Close()
|
||||
|
|
@ -91,11 +91,11 @@ func TestTermboxCloseIdempotent(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// TestTermboxSuspendThenClose verifies that a suspend (which calls finiScreen)
|
||||
// TestTcellScreenSuspendThenClose verifies that a suspend (which calls finiScreen)
|
||||
// followed by a permanent Close() works correctly — the doneCh should be
|
||||
// closed by Close() even though finiScreen was already called.
|
||||
func TestTermboxSuspendThenClose(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenSuspendThenClose(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -130,8 +130,8 @@ func TestTermboxSuspendThenClose(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTermboxResumeNoDeadlock(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenResumeNoDeadlock(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -158,8 +158,8 @@ func TestTermboxResumeNoDeadlock(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTermboxResumeDoesNotDropSend(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenResumeDoesNotDropSend(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -181,8 +181,8 @@ func TestTermboxResumeDoesNotDropSend(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTermboxResumeContextCancelled(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenResumeContextCancelled(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
|
@ -203,8 +203,8 @@ func TestTermboxResumeContextCancelled(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTermboxResumeContextCancelledWhileWaitingForReply(t *testing.T) {
|
||||
tb := NewTermbox()
|
||||
func TestTcellScreenResumeContextCancelledWhileWaitingForReply(t *testing.T) {
|
||||
tb := NewTcellScreen()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package peco
|
||||
|
||||
// PostInit is a no-op on Windows. tcell handles input mode automatically.
|
||||
func (t *Termbox) PostInit(cfg *Config) error {
|
||||
func (t *TcellScreen) PostInit(cfg *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue