Skip to content

Commit

Permalink
fix(term): simplify query methods
Browse files Browse the repository at this point in the history
- Remove less used features (reduce maintenance)
- Return query errors
- Customize query timeout
  • Loading branch information
aymanbagabas committed May 7, 2024
1 parent e72a49f commit 39d8441
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 104 deletions.
8 changes: 2 additions & 6 deletions exp/term/examples/query/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ import (

func main() {
in, out := os.Stdin, os.Stdout
hasKitty := term.SupportsKittyKeyboard(in, out)
hasKitty, _ := term.QueryKittyKeyboard(in, out)
log.Printf("Kitty keyboard support: %v", hasKitty)
bg := term.BackgroundColor(in, out)
bg, _ := term.QueryBackgroundColor(in, out)
log.Printf("Background color: %s", colorToHexString(bg))
fg := term.ForegroundColor(in, out)
log.Printf("Foreground color: %s", colorToHexString(fg))
cursor := term.CursorColor(in, out)
log.Printf("Cursor color: %s", colorToHexString(cursor))
}

// colorToHexString returns a hex string representation of a color.
Expand Down
143 changes: 49 additions & 94 deletions exp/term/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,118 +3,72 @@ package term
import (
"image/color"
"io"
"os"
"time"

"github.com/charmbracelet/x/exp/term/ansi"
"github.com/charmbracelet/x/exp/term/input"
)

// BackgroundColor queries the terminal for the background color.
// If the terminal does not support querying the background color, nil is
// returned.
func BackgroundColor(in, out *os.File) (c color.Color) {
state, err := MakeRaw(in.Fd())
if err != nil {
return
}

defer Restore(in.Fd(), state) // nolint: errcheck

// nolint: errcheck
QueryTerminal(in, out, func(events []input.Event) bool {
for _, e := range events {
switch e := e.(type) {
case input.BackgroundColorEvent:
c = e.Color
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
return false
}
}
return true
}, ansi.RequestBackgroundColor+ansi.RequestPrimaryDeviceAttributes)
return
// File represents a file that has a file descriptor.
type File interface {
Fd() uintptr
}

// ForegroundColor queries the terminal for the foreground color.
// If the terminal does not support querying the foreground color, nil is
// QueryBackgroundColor queries the terminal for the background color.
// If the terminal does not support querying the background color, nil is
// returned.
func ForegroundColor(in, out *os.File) (c color.Color) {
state, err := MakeRaw(in.Fd())
if err != nil {
return
}

defer Restore(in.Fd(), state) // nolint: errcheck

//
// Note: you will need to set the input to raw mode before calling this
// function.
//
// state, _ := term.MakeRaw(in.Fd())
// defer term.Restore(in.Fd(), state)
func QueryBackgroundColor(in io.Reader, out io.Writer) (c color.Color, err error) {
// nolint: errcheck
QueryTerminal(in, out, func(events []input.Event) bool {
for _, e := range events {
switch e := e.(type) {
case input.ForegroundColorEvent:
c = e.Color
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
return false
err = QueryTerminal(in, out, defaultQueryTimeout,
func(events []input.Event) bool {
for _, e := range events {
switch e := e.(type) {
case input.BackgroundColorEvent:
c = e.Color
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
return false
}
}
}
return true
}, ansi.RequestForegroundColor+ansi.RequestPrimaryDeviceAttributes)
return true
}, ansi.RequestBackgroundColor+ansi.RequestPrimaryDeviceAttributes)
return
}

// CursorColor queries the terminal for the cursor color.
// If the terminal does not support querying the cursor color, nil is returned.
func CursorColor(in, out *os.File) (c color.Color) {
state, err := MakeRaw(in.Fd())
if err != nil {
return
}

defer Restore(in.Fd(), state) // nolint: errcheck

// QueryKittyKeyboard returns the enabled Kitty keyboard protocol options.
// -1 means the terminal does not support the feature.
//
// Note: you will need to set the input to raw mode before calling this
// function.
//
// state, _ := term.MakeRaw(in.Fd())
// defer term.Restore(in.Fd(), state)
func QueryKittyKeyboard(in io.Reader, out io.Writer) (flags int, err error) {
flags = -1
// nolint: errcheck
QueryTerminal(in, out, func(events []input.Event) bool {
for _, e := range events {
switch e := e.(type) {
case input.CursorColorEvent:
c = e.Color
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
return false
err = QueryTerminal(in, out, defaultQueryTimeout,
func(events []input.Event) bool {
for _, e := range events {
switch event := e.(type) {
case input.KittyKeyboardEvent:
flags = int(event)
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
return false
}
}
}
return true
}, ansi.RequestCursorColor+ansi.RequestPrimaryDeviceAttributes)
return true
}, ansi.RequestKittyKeyboard+ansi.RequestPrimaryDeviceAttributes)
return
}

// SupportsKittyKeyboard returns true if the terminal supports the Kitty
// keyboard protocol.
func SupportsKittyKeyboard(in, out *os.File) (supported bool) {
state, err := MakeRaw(in.Fd())
if err != nil {
return
}

defer Restore(in.Fd(), state) // nolint: errcheck

// nolint: errcheck
QueryTerminal(in, out, func(events []input.Event) bool {
for _, e := range events {
switch e.(type) {
case input.KittyKeyboardEvent:
supported = true
continue // we need to consume the next DA1 event
case input.PrimaryDeviceAttributesEvent:
return false
}
}
return true
}, ansi.RequestKittyKeyboard+ansi.RequestPrimaryDeviceAttributes)
return
}
const defaultQueryTimeout = time.Second * 2

// QueryTerminalFilter is a function that filters input events using a type
// switch. If false is returned, the QueryTerminal function will stop reading
Expand All @@ -130,6 +84,7 @@ type QueryTerminalFilter func(events []input.Event) bool
func QueryTerminal(
in io.Reader,
out io.Writer,
timeout time.Duration,
filter QueryTerminalFilter,
query string,
) error {
Expand All @@ -145,7 +100,7 @@ func QueryTerminal(
go func() {
select {
case <-done:
case <-time.After(2 * time.Second):
case <-time.After(timeout):
rd.Cancel()
}
}()
Expand Down
6 changes: 2 additions & 4 deletions exp/term/terminal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (

func TestTerminalQueries(t *testing.T) {
in, out := os.Stdin, os.Stdout
_ = term.BackgroundColor(in, out)
_ = term.ForegroundColor(in, out)
_ = term.CursorColor(in, out)
_ = term.SupportsKittyKeyboard(in, out)
term.QueryBackgroundColor(in, out)
term.QueryKittyKeyboard(in, out)
}

0 comments on commit 39d8441

Please sign in to comment.