tty_io.go 3.8 KB

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