prompt_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package edit
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "time"
  7. "src.elv.sh/pkg/cli/clitest"
  8. "src.elv.sh/pkg/cli/term"
  9. "src.elv.sh/pkg/testutil"
  10. "src.elv.sh/pkg/ui"
  11. )
  12. func TestPrompt_ValueOutput(t *testing.T) {
  13. f := setup(t, rc(`set edit:prompt = { put '#'; num 13; styled '> ' red }`))
  14. f.TestTTY(t,
  15. "#13> ", Styles,
  16. " !!", term.DotHere)
  17. }
  18. func TestPrompt_ByteOutput(t *testing.T) {
  19. f := setup(t, rc(`set edit:prompt = { print 'bytes> ' }`))
  20. f.TestTTY(t, "bytes> ", term.DotHere)
  21. }
  22. func TestPrompt_ParsesSGRInByteOutput(t *testing.T) {
  23. f := setup(t, rc(`set edit:prompt = { print "\033[31mred\033[m> " }`))
  24. f.TestTTY(t,
  25. "red> ", Styles,
  26. "!!! ", term.DotHere)
  27. }
  28. func TestPrompt_NotifiesInvalidValueOutput(t *testing.T) {
  29. f := setup(t, rc(`set edit:prompt = { put good [bad] good2 }`))
  30. f.TestTTY(t, "goodgood2", term.DotHere)
  31. f.TestTTYNotes(t, "invalid output type from prompt: list")
  32. }
  33. func TestPrompt_NotifiesException(t *testing.T) {
  34. f := setup(t, rc(`set edit:prompt = { fail ERROR }`))
  35. f.TestTTYNotes(t,
  36. "[prompt error] ERROR\n",
  37. `see stack trace with "show $edit:exceptions[0]"`)
  38. evals(f.Evaler, `var excs = (count $edit:exceptions)`)
  39. testGlobal(t, f.Evaler, "excs", 1)
  40. }
  41. func TestRPrompt(t *testing.T) {
  42. f := setup(t, rc(`set edit:rprompt = { put 'RRR' }`))
  43. f.TestTTY(t, "~> ", term.DotHere,
  44. strings.Repeat(" ", clitest.FakeTTYWidth-6)+"RRR")
  45. }
  46. func TestPromptEagerness(t *testing.T) {
  47. f := setup(t, rc(
  48. `var i = 0`,
  49. `set edit:prompt = { set i = (+ $i 1); put $i'> ' }`,
  50. `set edit:-prompt-eagerness = 10`))
  51. f.TestTTY(t, "1> ", term.DotHere)
  52. // With eagerness = 10, any key press will cause the prompt to be
  53. // recomputed.
  54. f.TTYCtrl.Inject(term.K(ui.Backspace))
  55. f.TestTTY(t, "2> ", term.DotHere)
  56. }
  57. func TestPromptStaleThreshold(t *testing.T) {
  58. f := setup(t, rc(
  59. `var pipe = (file:pipe)`,
  60. `set edit:prompt = { nop (slurp < $pipe); put '> ' }`,
  61. `set edit:prompt-stale-threshold = `+scaledMsAsSec(50)))
  62. f.TestTTY(t,
  63. "???> ", Styles,
  64. "+++++", term.DotHere)
  65. evals(f.Evaler, `file:close $pipe[w]`)
  66. f.TestTTY(t, "> ", term.DotHere)
  67. evals(f.Evaler, `file:close $pipe[r]`)
  68. }
  69. func TestPromptStaleTransform(t *testing.T) {
  70. f := setup(t, rc(
  71. `var pipe = (file:pipe)`,
  72. `set edit:prompt = { nop (slurp < $pipe); put '> ' }`,
  73. `set edit:prompt-stale-threshold = `+scaledMsAsSec(50),
  74. `set edit:prompt-stale-transform = {|a| put S; put $a; put S }`))
  75. f.TestTTY(t, "S???> S", term.DotHere)
  76. evals(f.Evaler, `file:close $pipe[w]`)
  77. evals(f.Evaler, `file:close $pipe[r]`)
  78. }
  79. func TestPromptStaleTransform_Exception(t *testing.T) {
  80. f := setup(t, rc(
  81. `var pipe = (file:pipe)`,
  82. `set edit:prompt = { nop (slurp < $pipe); put '> ' }`,
  83. `set edit:prompt-stale-threshold = `+scaledMsAsSec(50),
  84. `set edit:prompt-stale-transform = {|_| fail ERROR }`))
  85. f.TestTTYNotes(t,
  86. "[prompt stale transform error] ERROR\n",
  87. `see stack trace with "show $edit:exceptions[0]"`)
  88. evals(f.Evaler, `var excs = (count $edit:exceptions)`)
  89. testGlobal(t, f.Evaler, "excs", 1)
  90. }
  91. func TestRPromptPersistent_True(t *testing.T) {
  92. testRPromptPersistent(t, `set edit:rprompt-persistent = $true`,
  93. "~> "+strings.Repeat(" ", clitest.FakeTTYWidth-6)+"RRR",
  94. "\n", term.DotHere,
  95. )
  96. }
  97. func TestRPromptPersistent_False(t *testing.T) {
  98. testRPromptPersistent(t, `set edit:rprompt-persistent = $false`,
  99. "~> ", // no rprompt
  100. "\n", term.DotHere,
  101. )
  102. }
  103. func testRPromptPersistent(t *testing.T, code string, finalBuf ...any) {
  104. f := setup(t, rc(`set edit:rprompt = { put RRR }`, code))
  105. // Make sure that the UI has stabilized before hitting Enter.
  106. f.TestTTY(t,
  107. "~> ", term.DotHere,
  108. strings.Repeat(" ", clitest.FakeTTYWidth-6), "RRR",
  109. )
  110. f.TTYCtrl.Inject(term.K('\n'))
  111. f.TestTTY(t, finalBuf...)
  112. }
  113. func TestDefaultPromptForNonRoot(t *testing.T) {
  114. f := setup(t, assign("edit:prompt", getDefaultPrompt(false)))
  115. f.TestTTY(t, "~> ", term.DotHere)
  116. }
  117. func TestDefaultPromptForRoot(t *testing.T) {
  118. f := setup(t, assign("edit:prompt", getDefaultPrompt(true)))
  119. f.TestTTY(t,
  120. "~# ", Styles,
  121. " !!", term.DotHere)
  122. }
  123. func TestDefaultRPrompt(t *testing.T) {
  124. f := setup(t, assign("edit:rprompt", getDefaultRPrompt("elf", "host")))
  125. f.TestTTY(t,
  126. "~> ", term.DotHere, strings.Repeat(" ", 39),
  127. "elf@host", Styles,
  128. "++++++++")
  129. }
  130. func scaledMsAsSec(ms int) string {
  131. return fmt.Sprint(testutil.Scaled(time.Duration(ms) * time.Millisecond).Seconds())
  132. }