You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
3.6 KiB

  1. package readline
  2. import (
  3. "io"
  4. "os"
  5. "sync"
  6. "sync/atomic"
  7. )
  8. var (
  9. Stdin io.ReadCloser = os.Stdin
  10. Stdout io.WriteCloser = os.Stdout
  11. Stderr io.WriteCloser = os.Stderr
  12. )
  13. var (
  14. std *Instance
  15. stdOnce sync.Once
  16. )
  17. // global instance will not submit history automatic
  18. func getInstance() *Instance {
  19. stdOnce.Do(func() {
  20. std, _ = NewEx(&Config{
  21. DisableAutoSaveHistory: true,
  22. })
  23. })
  24. return std
  25. }
  26. // let readline load history from filepath
  27. // and try to persist history into disk
  28. // set fp to "" to prevent readline persisting history to disk
  29. // so the `AddHistory` will return nil error forever.
  30. func SetHistoryPath(fp string) {
  31. ins := getInstance()
  32. cfg := ins.Config.Clone()
  33. cfg.HistoryFile = fp
  34. ins.SetConfig(cfg)
  35. }
  36. // set auto completer to global instance
  37. func SetAutoComplete(completer AutoCompleter) {
  38. ins := getInstance()
  39. cfg := ins.Config.Clone()
  40. cfg.AutoComplete = completer
  41. ins.SetConfig(cfg)
  42. }
  43. // add history to global instance manually
  44. // raise error only if `SetHistoryPath` is set with a non-empty path
  45. func AddHistory(content string) error {
  46. ins := getInstance()
  47. return ins.SaveHistory(content)
  48. }
  49. func Password(prompt string) ([]byte, error) {
  50. ins := getInstance()
  51. return ins.ReadPassword(prompt)
  52. }
  53. // readline with global configs
  54. func Line(prompt string) (string, error) {
  55. ins := getInstance()
  56. ins.SetPrompt(prompt)
  57. return ins.Readline()
  58. }
  59. type CancelableStdin struct {
  60. r io.Reader
  61. mutex sync.Mutex
  62. stop chan struct{}
  63. closed int32
  64. notify chan struct{}
  65. data []byte
  66. read int
  67. err error
  68. }
  69. func NewCancelableStdin(r io.Reader) *CancelableStdin {
  70. c := &CancelableStdin{
  71. r: r,
  72. notify: make(chan struct{}),
  73. stop: make(chan struct{}),
  74. }
  75. go c.ioloop()
  76. return c
  77. }
  78. func (c *CancelableStdin) ioloop() {
  79. loop:
  80. for {
  81. select {
  82. case <-c.notify:
  83. c.read, c.err = c.r.Read(c.data)
  84. select {
  85. case c.notify <- struct{}{}:
  86. case <-c.stop:
  87. break loop
  88. }
  89. case <-c.stop:
  90. break loop
  91. }
  92. }
  93. }
  94. func (c *CancelableStdin) Read(b []byte) (n int, err error) {
  95. c.mutex.Lock()
  96. defer c.mutex.Unlock()
  97. if atomic.LoadInt32(&c.closed) == 1 {
  98. return 0, io.EOF
  99. }
  100. c.data = b
  101. select {
  102. case c.notify <- struct{}{}:
  103. case <-c.stop:
  104. return 0, io.EOF
  105. }
  106. select {
  107. case <-c.notify:
  108. return c.read, c.err
  109. case <-c.stop:
  110. return 0, io.EOF
  111. }
  112. }
  113. func (c *CancelableStdin) Close() error {
  114. if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
  115. close(c.stop)
  116. }
  117. return nil
  118. }
  119. // FillableStdin is a stdin reader which can prepend some data before
  120. // reading into the real stdin
  121. type FillableStdin struct {
  122. sync.Mutex
  123. stdin io.Reader
  124. stdinBuffer io.ReadCloser
  125. buf []byte
  126. bufErr error
  127. }
  128. // NewFillableStdin gives you FillableStdin
  129. func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) {
  130. r, w := io.Pipe()
  131. s := &FillableStdin{
  132. stdinBuffer: r,
  133. stdin: stdin,
  134. }
  135. s.ioloop()
  136. return s, w
  137. }
  138. func (s *FillableStdin) ioloop() {
  139. go func() {
  140. for {
  141. bufR := make([]byte, 100)
  142. var n int
  143. n, s.bufErr = s.stdinBuffer.Read(bufR)
  144. if s.bufErr != nil {
  145. if s.bufErr == io.ErrClosedPipe {
  146. break
  147. }
  148. }
  149. s.Lock()
  150. s.buf = append(s.buf, bufR[:n]...)
  151. s.Unlock()
  152. }
  153. }()
  154. }
  155. // Read will read from the local buffer and if no data, read from stdin
  156. func (s *FillableStdin) Read(p []byte) (n int, err error) {
  157. s.Lock()
  158. i := len(s.buf)
  159. if len(p) < i {
  160. i = len(p)
  161. }
  162. if i > 0 {
  163. n := copy(p, s.buf)
  164. s.buf = s.buf[:0]
  165. cerr := s.bufErr
  166. s.bufErr = nil
  167. s.Unlock()
  168. return n, cerr
  169. }
  170. s.Unlock()
  171. n, err = s.stdin.Read(p)
  172. return n, err
  173. }
  174. func (s *FillableStdin) Close() error {
  175. s.stdinBuffer.Close()
  176. return nil
  177. }