terminal.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package term
  2. import (
  3. "image/color"
  4. "io"
  5. "time"
  6. "github.com/charmbracelet/x/ansi"
  7. "github.com/charmbracelet/x/input"
  8. )
  9. // File represents a file that has a file descriptor and can be read from,
  10. // written to, and closed.
  11. type File interface {
  12. io.ReadWriteCloser
  13. Fd() uintptr
  14. }
  15. // QueryBackgroundColor queries the terminal for the background color.
  16. // If the terminal does not support querying the background color, nil is
  17. // returned.
  18. //
  19. // Note: you will need to set the input to raw mode before calling this
  20. // function.
  21. //
  22. // state, _ := term.MakeRaw(in.Fd())
  23. // defer term.Restore(in.Fd(), state)
  24. func QueryBackgroundColor(in io.Reader, out io.Writer) (c color.Color, err error) {
  25. // nolint: errcheck
  26. err = QueryTerminal(in, out, defaultQueryTimeout,
  27. func(events []input.Event) bool {
  28. for _, e := range events {
  29. switch e := e.(type) {
  30. case input.BackgroundColorEvent:
  31. c = e.Color
  32. continue // we need to consume the next DA1 event
  33. case input.PrimaryDeviceAttributesEvent:
  34. return false
  35. }
  36. }
  37. return true
  38. }, ansi.RequestBackgroundColor+ansi.RequestPrimaryDeviceAttributes)
  39. return
  40. }
  41. // QueryKittyKeyboard returns the enabled Kitty keyboard protocol options.
  42. // -1 means the terminal does not support the feature.
  43. //
  44. // Note: you will need to set the input to raw mode before calling this
  45. // function.
  46. //
  47. // state, _ := term.MakeRaw(in.Fd())
  48. // defer term.Restore(in.Fd(), state)
  49. func QueryKittyKeyboard(in io.Reader, out io.Writer) (flags int, err error) {
  50. flags = -1
  51. // nolint: errcheck
  52. err = QueryTerminal(in, out, defaultQueryTimeout,
  53. func(events []input.Event) bool {
  54. for _, e := range events {
  55. switch event := e.(type) {
  56. case input.KittyKeyboardEvent:
  57. flags = int(event)
  58. continue // we need to consume the next DA1 event
  59. case input.PrimaryDeviceAttributesEvent:
  60. return false
  61. }
  62. }
  63. return true
  64. }, ansi.RequestKittyKeyboard+ansi.RequestPrimaryDeviceAttributes)
  65. return
  66. }
  67. const defaultQueryTimeout = time.Second * 2
  68. // QueryTerminalFilter is a function that filters input events using a type
  69. // switch. If false is returned, the QueryTerminal function will stop reading
  70. // input.
  71. type QueryTerminalFilter func(events []input.Event) bool
  72. // QueryTerminal queries the terminal for support of various features and
  73. // returns a list of response events.
  74. // Most of the time, you will need to set stdin to raw mode before calling this
  75. // function.
  76. // Note: This function will block until the terminal responds or the timeout
  77. // is reached.
  78. func QueryTerminal(
  79. in io.Reader,
  80. out io.Writer,
  81. timeout time.Duration,
  82. filter QueryTerminalFilter,
  83. query string,
  84. ) error {
  85. rd, err := input.NewDriver(in, "", 0)
  86. if err != nil {
  87. return err
  88. }
  89. defer rd.Close() // nolint: errcheck
  90. done := make(chan struct{}, 1)
  91. defer close(done)
  92. go func() {
  93. select {
  94. case <-done:
  95. case <-time.After(timeout):
  96. rd.Cancel()
  97. }
  98. }()
  99. if _, err := io.WriteString(out, query); err != nil {
  100. return err
  101. }
  102. for {
  103. events, err := rd.ReadEvents()
  104. if err != nil {
  105. return err
  106. }
  107. if !filter(events) {
  108. break
  109. }
  110. }
  111. return nil
  112. }