tty_io.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package at
  3. import (
  4. "encoding/json"
  5. "os"
  6. "time"
  7. "kitty/tools/tui/loop"
  8. "kitty/tools/utils"
  9. )
  10. type stream_response struct {
  11. Ok bool `json:"ok"`
  12. Stream bool `json:"stream"`
  13. }
  14. func is_stream_response(serialized_response []byte) bool {
  15. var response stream_response
  16. if len(serialized_response) > 32 {
  17. return false
  18. }
  19. err := json.Unmarshal(serialized_response, &response)
  20. return err == nil && response.Stream
  21. }
  22. func do_chunked_io(io_data *rc_io_data) (serialized_response []byte, err error) {
  23. serialized_response = make([]byte, 0)
  24. // we cant do inbandresize notification as in the --no-response case the
  25. // command can cause a resize and the loop can quit before the notification
  26. // arrives, leading to the notification being sent to whatever is executed
  27. // after us.
  28. lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoInBandResizeNotifications)
  29. if io_data.on_key_event != nil {
  30. lp.FullKeyboardProtocol()
  31. } else {
  32. lp.NoKeyboardStateChange()
  33. }
  34. if err != nil {
  35. return
  36. }
  37. const (
  38. BEFORE_FIRST_ESCAPE_CODE_SENT = iota
  39. WAITING_FOR_STREAMING_RESPONSE
  40. SENDING
  41. WAITING_FOR_RESPONSE
  42. )
  43. state := BEFORE_FIRST_ESCAPE_CODE_SENT
  44. var last_received_data_at time.Time
  45. var check_for_timeout func(timer_id loop.IdType) error
  46. wants_streaming := false
  47. check_for_timeout = func(timer_id loop.IdType) (err error) {
  48. if state != WAITING_FOR_RESPONSE && state != WAITING_FOR_STREAMING_RESPONSE {
  49. return
  50. }
  51. if io_data.on_key_event != nil {
  52. return
  53. }
  54. time_since_last_received_data := time.Since(last_received_data_at)
  55. if time_since_last_received_data >= io_data.timeout {
  56. return os.ErrDeadlineExceeded
  57. }
  58. _, err = lp.AddTimer(io_data.timeout-time_since_last_received_data, false, check_for_timeout)
  59. return
  60. }
  61. transition_to_read := func() {
  62. if state == WAITING_FOR_RESPONSE && io_data.rc.NoResponse {
  63. lp.Quit(0)
  64. }
  65. last_received_data_at = time.Now()
  66. _, _ = lp.AddTimer(io_data.timeout, false, check_for_timeout)
  67. }
  68. lp.OnReceivedData = func(data []byte) error {
  69. last_received_data_at = time.Now()
  70. return nil
  71. }
  72. queue_escape_code := func(data []byte) {
  73. lp.QueueWriteString(cmd_escape_code_prefix)
  74. lp.UnsafeQueueWriteBytes(data)
  75. lp.QueueWriteString(cmd_escape_code_suffix)
  76. }
  77. lp.OnInitialize = func() (string, error) {
  78. chunk, err := io_data.next_chunk()
  79. wants_streaming = io_data.rc.Stream
  80. if err != nil {
  81. if err == waiting_on_stdin {
  82. return "", nil
  83. }
  84. return "", err
  85. }
  86. if len(chunk) == 0 {
  87. state = WAITING_FOR_RESPONSE
  88. transition_to_read()
  89. } else {
  90. queue_escape_code(chunk)
  91. }
  92. return "", nil
  93. }
  94. lp.OnWriteComplete = func(completed_write_id loop.IdType, has_pending_writes bool) error {
  95. if state == WAITING_FOR_STREAMING_RESPONSE || state == WAITING_FOR_RESPONSE {
  96. return nil
  97. }
  98. chunk, err := io_data.next_chunk()
  99. if err != nil {
  100. if err == waiting_on_stdin {
  101. return nil
  102. }
  103. return err
  104. }
  105. if len(chunk) == 0 {
  106. state = utils.IfElse(state == BEFORE_FIRST_ESCAPE_CODE_SENT && wants_streaming, WAITING_FOR_STREAMING_RESPONSE, WAITING_FOR_RESPONSE)
  107. transition_to_read()
  108. } else {
  109. queue_escape_code(chunk)
  110. }
  111. if state == BEFORE_FIRST_ESCAPE_CODE_SENT {
  112. if wants_streaming {
  113. state = WAITING_FOR_STREAMING_RESPONSE
  114. transition_to_read()
  115. } else {
  116. state = SENDING
  117. }
  118. }
  119. return nil
  120. }
  121. lp.OnKeyEvent = func(event *loop.KeyEvent) error {
  122. if io_data.on_key_event == nil {
  123. return nil
  124. }
  125. err := io_data.on_key_event(lp, event)
  126. if err == end_reading_from_stdin {
  127. lp.Quit(0)
  128. return nil
  129. }
  130. if err != nil {
  131. return err
  132. }
  133. chunk, err := io_data.next_chunk()
  134. if err != nil {
  135. if err == waiting_on_stdin {
  136. return nil
  137. }
  138. return err
  139. }
  140. queue_escape_code(chunk)
  141. return err
  142. }
  143. lp.OnRCResponse = func(raw []byte) error {
  144. if state == WAITING_FOR_STREAMING_RESPONSE && is_stream_response(raw) {
  145. state = SENDING
  146. return lp.OnWriteComplete(0, false)
  147. }
  148. serialized_response = raw
  149. lp.Quit(0)
  150. return nil
  151. }
  152. err = lp.Run()
  153. if err == nil {
  154. lp.KillIfSignalled()
  155. }
  156. return
  157. }
  158. func do_tty_io(io_data *rc_io_data) (serialized_response []byte, err error) {
  159. return do_chunked_io(io_data)
  160. }