tty_io.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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. lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors)
  25. if io_data.on_key_event != nil {
  26. lp.FullKeyboardProtocol()
  27. } else {
  28. lp.NoKeyboardStateChange()
  29. }
  30. if err != nil {
  31. return
  32. }
  33. const (
  34. BEFORE_FIRST_ESCAPE_CODE_SENT = iota
  35. WAITING_FOR_STREAMING_RESPONSE
  36. SENDING
  37. WAITING_FOR_RESPONSE
  38. )
  39. state := BEFORE_FIRST_ESCAPE_CODE_SENT
  40. var last_received_data_at time.Time
  41. var check_for_timeout func(timer_id loop.IdType) error
  42. wants_streaming := false
  43. check_for_timeout = func(timer_id loop.IdType) (err error) {
  44. if state != WAITING_FOR_RESPONSE && state != WAITING_FOR_STREAMING_RESPONSE {
  45. return
  46. }
  47. if io_data.on_key_event != nil {
  48. return
  49. }
  50. time_since_last_received_data := time.Since(last_received_data_at)
  51. if time_since_last_received_data >= io_data.timeout {
  52. return os.ErrDeadlineExceeded
  53. }
  54. _, err = lp.AddTimer(io_data.timeout-time_since_last_received_data, false, check_for_timeout)
  55. return
  56. }
  57. transition_to_read := func() {
  58. if state == WAITING_FOR_RESPONSE && io_data.rc.NoResponse {
  59. lp.Quit(0)
  60. }
  61. last_received_data_at = time.Now()
  62. _, _ = lp.AddTimer(io_data.timeout, false, check_for_timeout)
  63. }
  64. lp.OnReceivedData = func(data []byte) error {
  65. last_received_data_at = time.Now()
  66. return nil
  67. }
  68. queue_escape_code := func(data []byte) {
  69. lp.QueueWriteString(cmd_escape_code_prefix)
  70. lp.UnsafeQueueWriteBytes(data)
  71. lp.QueueWriteString(cmd_escape_code_suffix)
  72. }
  73. lp.OnInitialize = func() (string, error) {
  74. chunk, err := io_data.next_chunk()
  75. wants_streaming = io_data.rc.Stream
  76. if err != nil {
  77. if err == waiting_on_stdin {
  78. return "", nil
  79. }
  80. return "", err
  81. }
  82. if len(chunk) == 0 {
  83. state = WAITING_FOR_RESPONSE
  84. transition_to_read()
  85. } else {
  86. queue_escape_code(chunk)
  87. }
  88. return "", nil
  89. }
  90. lp.OnWriteComplete = func(completed_write_id loop.IdType, has_pending_writes bool) error {
  91. if state == WAITING_FOR_STREAMING_RESPONSE || state == WAITING_FOR_RESPONSE {
  92. return nil
  93. }
  94. chunk, err := io_data.next_chunk()
  95. if err != nil {
  96. if err == waiting_on_stdin {
  97. return nil
  98. }
  99. return err
  100. }
  101. if len(chunk) == 0 {
  102. state = utils.IfElse(state == BEFORE_FIRST_ESCAPE_CODE_SENT && wants_streaming, WAITING_FOR_STREAMING_RESPONSE, WAITING_FOR_RESPONSE)
  103. transition_to_read()
  104. } else {
  105. queue_escape_code(chunk)
  106. }
  107. if state == BEFORE_FIRST_ESCAPE_CODE_SENT {
  108. if wants_streaming {
  109. state = WAITING_FOR_STREAMING_RESPONSE
  110. transition_to_read()
  111. } else {
  112. state = SENDING
  113. }
  114. }
  115. return nil
  116. }
  117. lp.OnKeyEvent = func(event *loop.KeyEvent) error {
  118. if io_data.on_key_event == nil {
  119. return nil
  120. }
  121. err := io_data.on_key_event(lp, event)
  122. if err == end_reading_from_stdin {
  123. lp.Quit(0)
  124. return nil
  125. }
  126. if err != nil {
  127. return err
  128. }
  129. chunk, err := io_data.next_chunk()
  130. if err != nil {
  131. if err == waiting_on_stdin {
  132. return nil
  133. }
  134. return err
  135. }
  136. queue_escape_code(chunk)
  137. return err
  138. }
  139. lp.OnRCResponse = func(raw []byte) error {
  140. if state == WAITING_FOR_STREAMING_RESPONSE && is_stream_response(raw) {
  141. state = SENDING
  142. return lp.OnWriteComplete(0, false)
  143. }
  144. serialized_response = raw
  145. lp.Quit(0)
  146. return nil
  147. }
  148. err = lp.Run()
  149. if err == nil {
  150. lp.KillIfSignalled()
  151. }
  152. return
  153. }
  154. func do_tty_io(io_data *rc_io_data) (serialized_response []byte, err error) {
  155. return do_chunked_io(io_data)
  156. }