actions_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package readline
  3. import (
  4. "container/list"
  5. "fmt"
  6. "kitty/tools/cli"
  7. "kitty/tools/tui/loop"
  8. "kitty/tools/utils/shlex"
  9. "strconv"
  10. "strings"
  11. "testing"
  12. "github.com/google/go-cmp/cmp"
  13. )
  14. var _ = fmt.Print
  15. func new_rl() *Readline {
  16. lp, _ := loop.New()
  17. rl := New(lp, RlInit{Prompt: "$$ "})
  18. rl.screen_width = 10
  19. rl.screen_height = 100
  20. return rl
  21. }
  22. func test_func(t *testing.T) func(string, func(*Readline), ...string) *Readline {
  23. return func(initial string, prepare func(rl *Readline), expected ...string) *Readline {
  24. rl := new_rl()
  25. rl.add_text(initial)
  26. if prepare != nil {
  27. prepare(rl)
  28. }
  29. if len(expected) > 0 {
  30. if expected[0] != rl.text_upto_cursor_pos() {
  31. t.Fatalf("Text upto cursor pos not as expected for: %#v\n%#v != %#v", initial, expected[0], rl.text_upto_cursor_pos())
  32. }
  33. }
  34. if len(expected) > 1 {
  35. if expected[1] != rl.text_after_cursor_pos() {
  36. t.Fatalf("Text after cursor pos not as expected for: %#v\n%#v != %#v", initial, expected[1], rl.text_after_cursor_pos())
  37. }
  38. }
  39. if len(expected) > 2 {
  40. if expected[2] != rl.all_text() {
  41. t.Fatalf("Text not as expected for: %#v\n%#v != %#v", initial, expected[2], rl.all_text())
  42. }
  43. }
  44. return rl
  45. }
  46. }
  47. func TestAddText(t *testing.T) {
  48. dt := test_func(t)
  49. dt("test", nil, "test", "", "test")
  50. dt("1234\n", nil, "1234\n", "", "1234\n")
  51. dt("abcd", func(rl *Readline) {
  52. rl.input_state.cursor.X = 2
  53. rl.add_text("12")
  54. }, "ab12", "cd", "ab12cd")
  55. dt("abcd", func(rl *Readline) {
  56. rl.input_state.cursor.X = 2
  57. rl.add_text("12\n34")
  58. }, "ab12\n34", "cd", "ab12\n34cd")
  59. dt("abcd\nxyz", func(rl *Readline) {
  60. rl.input_state.cursor.X = 2
  61. rl.add_text("12\n34")
  62. }, "abcd\nxy12\n34", "z", "abcd\nxy12\n34z")
  63. }
  64. func TestGetScreenLines(t *testing.T) {
  65. rl := new_rl()
  66. p := func(primary bool) Prompt {
  67. if primary {
  68. return rl.prompt
  69. }
  70. return rl.continuation_prompt
  71. }
  72. tsl := func(expected ...ScreenLine) {
  73. q := rl.get_screen_lines()
  74. actual := make([]ScreenLine, len(q))
  75. for i, x := range q {
  76. actual[i] = *x
  77. }
  78. if diff := cmp.Diff(expected, actual); diff != "" {
  79. t.Fatalf("Did not get expected screen lines for: %#v and cursor: %+v\n%s", rl.AllText(), rl.input_state.cursor, diff)
  80. }
  81. }
  82. tsl(ScreenLine{Prompt: p(true), CursorCell: 3, AfterLineBreak: true})
  83. rl.add_text("123")
  84. tsl(ScreenLine{Prompt: p(true), CursorCell: 6, Text: "123", CursorTextPos: 3, TextLengthInCells: 3, AfterLineBreak: true})
  85. rl.add_text("456")
  86. tsl(ScreenLine{Prompt: p(true), CursorCell: 9, Text: "123456", CursorTextPos: 6, TextLengthInCells: 6, AfterLineBreak: true})
  87. rl.add_text("7")
  88. tsl(
  89. ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7, AfterLineBreak: true},
  90. ScreenLine{OffsetInParentLine: 7},
  91. )
  92. rl.add_text("89")
  93. tsl(
  94. ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7, AfterLineBreak: true},
  95. ScreenLine{OffsetInParentLine: 7, Text: "89", CursorCell: 2, TextLengthInCells: 2, CursorTextPos: 2},
  96. )
  97. rl.ResetText()
  98. rl.add_text("123\n456abcdeXYZ")
  99. tsl(
  100. ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3, AfterLineBreak: true},
  101. ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1, AfterLineBreak: true},
  102. ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: 3, CursorTextPos: 3, Text: "XYZ"},
  103. )
  104. rl.input_state.cursor = Position{X: 2}
  105. tsl(
  106. ScreenLine{Prompt: p(true), CursorCell: 5, Text: "123", CursorTextPos: 2, TextLengthInCells: 3, AfterLineBreak: true},
  107. ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1, AfterLineBreak: true},
  108. ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: -1, CursorTextPos: -1, Text: "XYZ"},
  109. )
  110. rl.input_state.cursor = Position{X: 2, Y: 1}
  111. tsl(
  112. ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3, AfterLineBreak: true},
  113. ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: 4, CursorTextPos: 2, AfterLineBreak: true},
  114. ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: -1, CursorTextPos: -1, Text: "XYZ"},
  115. )
  116. rl.input_state.cursor = Position{X: 8, Y: 1}
  117. tsl(
  118. ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3, AfterLineBreak: true},
  119. ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1, AfterLineBreak: true},
  120. ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: 0, CursorTextPos: 0, Text: "XYZ"},
  121. )
  122. rl.ResetText()
  123. rl.add_text("1234567\nabc")
  124. rl.input_state.cursor = Position{X: 7}
  125. tsl(
  126. ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7, AfterLineBreak: true},
  127. ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "abc", CursorCell: 2, TextLengthInCells: 3, CursorTextPos: 0, AfterLineBreak: true},
  128. )
  129. }
  130. func TestCursorMovement(t *testing.T) {
  131. dt := test_func(t)
  132. left := func(rl *Readline, amt uint, moved_amt uint, traverse_line_breaks bool) {
  133. actual := rl.move_cursor_left(amt, traverse_line_breaks)
  134. if actual != moved_amt {
  135. t.Fatalf("Failed to move cursor by %#v\nactual != expected: %#v != %#v", amt, actual, moved_amt)
  136. }
  137. }
  138. dt("one\ntwo", func(rl *Readline) {
  139. left(rl, 2, 2, false)
  140. }, "one\nt", "wo")
  141. dt("one\ntwo", func(rl *Readline) {
  142. left(rl, 4, 3, false)
  143. }, "one\n", "two")
  144. dt("one\ntwo", func(rl *Readline) {
  145. left(rl, 4, 4, true)
  146. }, "one", "\ntwo")
  147. dt("one\ntwo", func(rl *Readline) {
  148. left(rl, 7, 7, true)
  149. }, "", "one\ntwo")
  150. dt("one\ntwo", func(rl *Readline) {
  151. left(rl, 10, 7, true)
  152. }, "", "one\ntwo")
  153. dt("one😀", func(rl *Readline) {
  154. left(rl, 1, 1, false)
  155. }, "one", "😀")
  156. dt("oneà", func(rl *Readline) {
  157. left(rl, 1, 1, false)
  158. }, "one", "à")
  159. right := func(rl *Readline, amt uint, moved_amt uint, traverse_line_breaks bool) {
  160. rl.input_state.cursor.Y = 0
  161. rl.input_state.cursor.X = 0
  162. actual := rl.move_cursor_right(amt, traverse_line_breaks)
  163. if actual != moved_amt {
  164. t.Fatalf("Failed to move cursor by %d\nactual != expected: %d != %d", amt, actual, moved_amt)
  165. }
  166. }
  167. dt("one\ntwo", func(rl *Readline) {
  168. right(rl, 2, 2, false)
  169. }, "on", "e\ntwo")
  170. dt("one\ntwo", func(rl *Readline) {
  171. right(rl, 4, 3, false)
  172. }, "one", "\ntwo")
  173. dt("one\ntwo", func(rl *Readline) {
  174. right(rl, 4, 4, true)
  175. }, "one\n", "two")
  176. dt("😀one", func(rl *Readline) {
  177. right(rl, 1, 1, false)
  178. }, "😀", "one")
  179. dt("àb", func(rl *Readline) {
  180. right(rl, 1, 1, false)
  181. }, "à", "b")
  182. rl := new_rl()
  183. vert := func(amt int, moved_amt int, text_upto_cursor_pos string, initials ...Position) {
  184. initial := Position{}
  185. if len(initials) > 0 {
  186. initial = initials[0]
  187. }
  188. rl.input_state.cursor = initial
  189. actual := rl.move_cursor_vertically(amt)
  190. if actual != moved_amt {
  191. t.Fatalf("Failed to move cursor by %#v for: %#v \nactual != expected: %#v != %#v", amt, rl.AllText(), actual, moved_amt)
  192. }
  193. if diff := cmp.Diff(text_upto_cursor_pos, rl.text_upto_cursor_pos()); diff != "" {
  194. t.Fatalf("Did not get expected screen lines for: %#v and cursor: %+v\n%s", rl.AllText(), initial, diff)
  195. }
  196. }
  197. rl.ResetText()
  198. rl.add_text("1234567xy\nabcd\n123")
  199. vert(-1, -1, "1234567xy\nabc", Position{X: 3, Y: 2})
  200. vert(-2, -2, "1234567xy", Position{X: 3, Y: 2})
  201. vert(-30, -3, "123", Position{X: 3, Y: 2})
  202. rl.ResetText()
  203. rl.add_text("o\u0300ne two three\nfour five")
  204. wf := func(amt uint, expected_amt uint, text_before_cursor string) {
  205. pos := rl.input_state.cursor
  206. actual_amt := rl.move_to_end_of_word(amt, true, has_word_chars)
  207. if actual_amt != expected_amt {
  208. t.Fatalf("Failed to move to word end, expected amt (%d) != actual amt (%d)", expected_amt, actual_amt)
  209. }
  210. if diff := cmp.Diff(text_before_cursor, rl.TextBeforeCursor()); diff != "" {
  211. t.Fatalf("Did not get expected text before cursor for: %#v and cursor: %+v\n%s", rl.AllText(), pos, diff)
  212. }
  213. }
  214. rl.input_state.cursor = Position{}
  215. wf(1, 1, "òne")
  216. wf(1, 1, "òne two")
  217. wf(1, 1, "òne two three")
  218. wf(1, 1, "òne two three\nfour")
  219. wf(1, 1, "òne two three\nfour five")
  220. wf(1, 0, "òne two three\nfour five")
  221. rl.input_state.cursor = Position{}
  222. wf(5, 5, "òne two three\nfour five")
  223. rl.input_state.cursor = Position{X: 5}
  224. wf(1, 1, "òne two")
  225. wb := func(amt uint, expected_amt uint, text_before_cursor string) {
  226. pos := rl.input_state.cursor
  227. actual_amt := rl.move_to_start_of_word(amt, true, has_word_chars)
  228. if actual_amt != expected_amt {
  229. t.Fatalf("Failed to move to word end, expected amt (%d) != actual amt (%d)", expected_amt, actual_amt)
  230. }
  231. if diff := cmp.Diff(text_before_cursor, rl.TextBeforeCursor()); diff != "" {
  232. t.Fatalf("Did not get expected text before cursor for: %#v and cursor: %+v\n%s", rl.AllText(), pos, diff)
  233. }
  234. }
  235. rl.input_state.cursor = Position{X: 2}
  236. wb(1, 1, "")
  237. rl.input_state.cursor = Position{X: 8, Y: 1}
  238. wb(1, 1, "òne two three\nfour ")
  239. wb(1, 1, "òne two three\n")
  240. wb(1, 1, "òne two ")
  241. wb(1, 1, "òne ")
  242. wb(1, 1, "")
  243. wb(1, 0, "")
  244. rl.input_state.cursor = Position{X: 8, Y: 1}
  245. wb(5, 5, "")
  246. rl.input_state.cursor = Position{X: 5}
  247. wb(1, 1, "")
  248. }
  249. func TestYanking(t *testing.T) {
  250. rl := new_rl()
  251. as_slice := func(l *list.List) []string {
  252. ans := make([]string, 0, l.Len())
  253. for e := l.Front(); e != nil; e = e.Next() {
  254. ans = append(ans, e.Value.(string))
  255. }
  256. return ans
  257. }
  258. assert_items := func(expected ...string) {
  259. if diff := cmp.Diff(expected, as_slice(rl.kill_ring.items)); diff != "" {
  260. t.Fatalf("kill ring items not as expected\n%s", diff)
  261. }
  262. }
  263. assert_text := func(expected string) {
  264. if diff := cmp.Diff(expected, rl.all_text()); diff != "" {
  265. t.Fatalf("text not as expected:\n%s", diff)
  266. }
  267. }
  268. rl.add_text("1 2 3\none two three")
  269. rl.perform_action(ActionKillToStartOfLine, 1)
  270. assert_items("one two three")
  271. rl.perform_action(ActionCursorUp, 1)
  272. rl.perform_action(ActionKillToEndOfLine, 1)
  273. assert_items("1 2 3", "one two three")
  274. rl.perform_action(ActionYank, 1)
  275. assert_text("1 2 3\n")
  276. rl.perform_action(ActionYank, 1)
  277. assert_text("1 2 31 2 3\n")
  278. rl.perform_action(ActionPopYank, 1)
  279. assert_text("1 2 3one two three\n")
  280. rl.perform_action(ActionPopYank, 1)
  281. assert_text("1 2 31 2 3\n")
  282. rl.ResetText()
  283. rl.kill_ring.clear()
  284. rl.add_text("one two three")
  285. rl.perform_action(ActionMoveToStartOfLine, 1)
  286. rl.perform_action(ActionKillNextWord, 1)
  287. assert_items("one")
  288. assert_text(" two three")
  289. rl.perform_action(ActionKillNextWord, 1)
  290. assert_items("one two")
  291. assert_text(" three")
  292. rl.perform_action(ActionCursorRight, 1)
  293. rl.perform_action(ActionKillNextWord, 1)
  294. assert_items("three", "one two")
  295. assert_text(" ")
  296. }
  297. func TestEraseChars(t *testing.T) {
  298. dt := test_func(t)
  299. backspace := func(rl *Readline, amt uint, erased_amt uint, traverse_line_breaks bool) {
  300. actual := rl.erase_chars_before_cursor(amt, traverse_line_breaks)
  301. if actual != erased_amt {
  302. t.Fatalf("Failed to move cursor by %#v\nactual != expected: %d != %d", amt, actual, erased_amt)
  303. }
  304. }
  305. dt("one\ntwo", func(rl *Readline) {
  306. backspace(rl, 2, 2, false)
  307. }, "one\nt", "")
  308. dt("one\ntwo", func(rl *Readline) {
  309. rl.input_state.cursor.X = 1
  310. backspace(rl, 2, 1, false)
  311. }, "one\n", "wo")
  312. dt("one\ntwo", func(rl *Readline) {
  313. rl.input_state.cursor.X = 1
  314. backspace(rl, 2, 2, true)
  315. }, "one", "wo")
  316. dt("a😀", func(rl *Readline) {
  317. backspace(rl, 1, 1, false)
  318. }, "a", "")
  319. dt("bà", func(rl *Readline) {
  320. backspace(rl, 1, 1, false)
  321. }, "b", "")
  322. del := func(rl *Readline, amt uint, erased_amt uint, traverse_line_breaks bool) {
  323. rl.input_state.cursor.Y = 0
  324. rl.input_state.cursor.X = 0
  325. actual := rl.erase_chars_after_cursor(amt, traverse_line_breaks)
  326. if actual != erased_amt {
  327. t.Fatalf("Failed to move cursor by %#v\nactual != expected: %d != %d", amt, actual, erased_amt)
  328. }
  329. }
  330. dt("one\ntwo", func(rl *Readline) {
  331. del(rl, 2, 2, false)
  332. }, "", "e\ntwo")
  333. dt("😀a", func(rl *Readline) {
  334. del(rl, 1, 1, false)
  335. }, "", "a")
  336. dt("àb", func(rl *Readline) {
  337. del(rl, 1, 1, false)
  338. }, "", "b")
  339. dt("one\ntwo\nthree", func(rl *Readline) {
  340. rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
  341. }, "oree", "")
  342. dt("one\ntwo\nthree", func(rl *Readline) {
  343. rl.input_state.cursor.X = 1
  344. rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
  345. }, "o", "ree")
  346. dt("one\ntwo\nthree", func(rl *Readline) {
  347. rl.input_state.cursor = Position{X: 1, Y: 1}
  348. rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
  349. }, "o", "ree")
  350. dt("one\ntwo\nthree", func(rl *Readline) {
  351. rl.input_state.cursor = Position{X: 1, Y: 0}
  352. rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
  353. }, "o", "ree")
  354. dt("one\ntwo\nthree", func(rl *Readline) {
  355. rl.input_state.cursor = Position{X: 0, Y: 0}
  356. rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
  357. }, "", "oree")
  358. }
  359. func TestNumberArgument(t *testing.T) {
  360. rl := new_rl()
  361. rl.screen_width = 100
  362. test := func(ac Action, before_cursor, after_cursor string) {
  363. rl.dispatch_key_action(ac)
  364. if diff := cmp.Diff(before_cursor, rl.text_upto_cursor_pos()); diff != "" {
  365. t.Fatalf("The text before the cursor was not as expected for action: %#v\n%s", ac, diff)
  366. }
  367. if diff := cmp.Diff(after_cursor, rl.text_after_cursor_pos()); diff != "" {
  368. t.Fatalf("The text after the cursor was not as expected for action: %#v\n%s", ac, diff)
  369. }
  370. }
  371. sw := func(num int) {
  372. q := rl.format_arg_prompt(strconv.Itoa(num))
  373. for _, sl := range rl.get_screen_lines() {
  374. if num <= 0 && !strings.Contains(sl.Prompt.Text, "$$") {
  375. t.Fatalf("arg prompt unexpectedly present for: %#v", rl.AllText())
  376. }
  377. if num > 0 && !strings.Contains(sl.Prompt.Text, q) {
  378. t.Fatalf("arg prompt unexpectedly not present for: %#v prompt: %#v", rl.AllText(), sl.Prompt.Text)
  379. }
  380. }
  381. }
  382. sw(0)
  383. rl.dispatch_key_action(ActionNumericArgumentDigit1)
  384. sw(1)
  385. rl.dispatch_key_action(ActionNumericArgumentDigit0)
  386. sw(10)
  387. rl.text_to_be_added = "x"
  388. test(ActionAddText, "xxxxxxxxxx", "")
  389. sw(0)
  390. test(ActionNumericArgumentDigit0, "xxxxxxxxxx0", "")
  391. sw(0)
  392. rl.dispatch_key_action(ActionNumericArgumentDigit1)
  393. test(ActionNumericArgumentDigitMinus, "xxxxxxxxxx0-", "")
  394. sw(0)
  395. rl.dispatch_key_action(ActionNumericArgumentDigit1)
  396. sw(1)
  397. rl.dispatch_key_action(ActionNumericArgumentDigit1)
  398. sw(11)
  399. test(ActionCursorLeft, "x", "xxxxxxxxx0-")
  400. sw(0)
  401. }
  402. func TestHistory(t *testing.T) {
  403. rl := new_rl()
  404. add_item := func(x string) {
  405. rl.history.AddItem(x, 0)
  406. }
  407. add_item("a one")
  408. add_item("a two")
  409. add_item("b three")
  410. add_item("b four")
  411. test := func(ac Action, before_cursor, after_cursor string) {
  412. rl.perform_action(ac, 1)
  413. if diff := cmp.Diff(before_cursor, rl.text_upto_cursor_pos()); diff != "" {
  414. t.Fatalf("The text before the cursor was not as expected for action: %#v\n%s", ac, diff)
  415. }
  416. if diff := cmp.Diff(after_cursor, rl.text_after_cursor_pos()); diff != "" {
  417. t.Fatalf("The text after the cursor was not as expected for action: %#v\n%s", ac, diff)
  418. }
  419. }
  420. test(ActionHistoryPreviousOrCursorUp, "b four", "")
  421. test(ActionHistoryPreviousOrCursorUp, "b three", "")
  422. test(ActionHistoryPrevious, "a two", "")
  423. test(ActionHistoryPrevious, "a one", "")
  424. test(ActionHistoryPrevious, "a one", "")
  425. test(ActionHistoryNext, "a two", "")
  426. test(ActionHistoryNext, "b three", "")
  427. test(ActionHistoryNext, "b four", "")
  428. test(ActionHistoryNext, "", "")
  429. test(ActionHistoryNext, "", "")
  430. test(ActionHistoryPrevious, "b four", "")
  431. test(ActionHistoryPrevious, "b three", "")
  432. test(ActionHistoryNext, "b four", "")
  433. rl.ResetText()
  434. rl.add_text("a")
  435. test(ActionHistoryPrevious, "a two", "")
  436. test(ActionHistoryPrevious, "a one", "")
  437. test(ActionHistoryPrevious, "a one", "")
  438. test(ActionHistoryNext, "a two", "")
  439. test(ActionHistoryNext, "a", "")
  440. test(ActionHistoryNext, "a", "")
  441. ah := func(before_cursor, after_cursor string) {
  442. ab := rl.text_upto_cursor_pos()
  443. aa := rl.text_after_cursor_pos()
  444. if diff := cmp.Diff(before_cursor, ab); diff != "" {
  445. t.Fatalf("Text before cursor not as expected:\n%s", diff)
  446. }
  447. if diff := cmp.Diff(after_cursor, aa); diff != "" {
  448. t.Fatalf("Text after cursor not as expected:\n%s", diff)
  449. }
  450. }
  451. add_item("xyz1")
  452. add_item("xyz2")
  453. add_item("xyz11")
  454. rl.perform_action(ActionHistoryIncrementalSearchBackwards, 1)
  455. ah("", "")
  456. rl.text_to_be_added = "z"
  457. rl.perform_action(ActionAddText, 1)
  458. ah("xy", "z11")
  459. rl.text_to_be_added = "2"
  460. rl.perform_action(ActionAddText, 1)
  461. ah("xy", "z2")
  462. rl.text_to_be_added = "m"
  463. rl.perform_action(ActionAddText, 1)
  464. ah("No matches for: z2m", "")
  465. rl.perform_action(ActionBackspace, 1)
  466. ah("xy", "z2")
  467. rl.perform_action(ActionBackspace, 1)
  468. ah("xy", "z2")
  469. rl.perform_action(ActionHistoryIncrementalSearchBackwards, 1)
  470. ah("xy", "z1")
  471. rl.perform_action(ActionHistoryIncrementalSearchBackwards, 1)
  472. ah("xy", "z1")
  473. rl.perform_action(ActionHistoryIncrementalSearchForwards, 1)
  474. ah("xy", "z2")
  475. rl.perform_action(ActionTerminateHistorySearchAndRestore, 1)
  476. ah("a", "")
  477. }
  478. func TestReadlineCompletion(t *testing.T) {
  479. completer := func(before_cursor, after_cursor string) (ans *cli.Completions) {
  480. root := cli.NewRootCommand()
  481. c := root.AddSubCommand(&cli.Command{Name: "test-completion"})
  482. c.AddSubCommand(&cli.Command{Name: "a1"})
  483. c.AddSubCommand(&cli.Command{Name: "a11"})
  484. c.AddSubCommand(&cli.Command{Name: "a2"})
  485. prefix := c.Name + " "
  486. text := prefix + before_cursor
  487. argv, position_of_last_arg := shlex.SplitForCompletion(text)
  488. if len(argv) == 0 || position_of_last_arg < len(prefix) {
  489. return
  490. }
  491. ans = root.GetCompletions(argv, nil)
  492. ans.CurrentWordIdx = position_of_last_arg - len(prefix)
  493. return
  494. }
  495. rl := new_rl()
  496. rl.completions.completer = completer
  497. ah := func(before_cursor, after_cursor string) {
  498. ab := rl.text_upto_cursor_pos()
  499. aa := rl.text_after_cursor_pos()
  500. if diff := cmp.Diff(before_cursor, ab); diff != "" {
  501. t.Fatalf("Text before cursor not as expected:\n%s", diff)
  502. }
  503. if diff := cmp.Diff(after_cursor, aa); diff != "" {
  504. t.Fatalf("Text after cursor not as expected:\n%s", diff)
  505. }
  506. actual, _ := rl.completion_screen_lines()
  507. expected := []string{"a1 a11 a2 "}
  508. if diff := cmp.Diff(expected, actual[1:]); diff != "" {
  509. t.Fatalf("Completion screen lines not as expected:\n%s", diff)
  510. }
  511. }
  512. rl.add_text("a")
  513. rl.perform_action(ActionCompleteForward, 1)
  514. ah("a", "")
  515. rl.perform_action(ActionCompleteForward, 1)
  516. ah("a1 ", "")
  517. rl.perform_action(ActionCompleteForward, 1)
  518. ah("a11 ", "")
  519. rl.perform_action(ActionCompleteForward, 1)
  520. ah("a2 ", "")
  521. rl.perform_action(ActionCompleteBackward, 1)
  522. ah("a11 ", "")
  523. }