test_completion.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. from __future__ import annotations
  2. import os
  3. import re
  4. import shutil
  5. import tempfile
  6. from contextlib import contextmanager
  7. from prompt_toolkit.completion import (
  8. CompleteEvent,
  9. FuzzyWordCompleter,
  10. NestedCompleter,
  11. PathCompleter,
  12. WordCompleter,
  13. merge_completers,
  14. )
  15. from prompt_toolkit.document import Document
  16. @contextmanager
  17. def chdir(directory):
  18. """Context manager for current working directory temporary change."""
  19. orig_dir = os.getcwd()
  20. os.chdir(directory)
  21. try:
  22. yield
  23. finally:
  24. os.chdir(orig_dir)
  25. def write_test_files(test_dir, names=None):
  26. """Write test files in test_dir using the names list."""
  27. names = names or range(10)
  28. for i in names:
  29. with open(os.path.join(test_dir, str(i)), "wb") as out:
  30. out.write(b"")
  31. def test_pathcompleter_completes_in_current_directory():
  32. completer = PathCompleter()
  33. doc_text = ""
  34. doc = Document(doc_text, len(doc_text))
  35. event = CompleteEvent()
  36. completions = list(completer.get_completions(doc, event))
  37. assert len(completions) > 0
  38. def test_pathcompleter_completes_files_in_current_directory():
  39. # setup: create a test dir with 10 files
  40. test_dir = tempfile.mkdtemp()
  41. write_test_files(test_dir)
  42. expected = sorted(str(i) for i in range(10))
  43. if not test_dir.endswith(os.path.sep):
  44. test_dir += os.path.sep
  45. with chdir(test_dir):
  46. completer = PathCompleter()
  47. # this should complete on the cwd
  48. doc_text = ""
  49. doc = Document(doc_text, len(doc_text))
  50. event = CompleteEvent()
  51. completions = list(completer.get_completions(doc, event))
  52. result = sorted(c.text for c in completions)
  53. assert expected == result
  54. # cleanup
  55. shutil.rmtree(test_dir)
  56. def test_pathcompleter_completes_files_in_absolute_directory():
  57. # setup: create a test dir with 10 files
  58. test_dir = tempfile.mkdtemp()
  59. write_test_files(test_dir)
  60. expected = sorted(str(i) for i in range(10))
  61. test_dir = os.path.abspath(test_dir)
  62. if not test_dir.endswith(os.path.sep):
  63. test_dir += os.path.sep
  64. completer = PathCompleter()
  65. # force unicode
  66. doc_text = str(test_dir)
  67. doc = Document(doc_text, len(doc_text))
  68. event = CompleteEvent()
  69. completions = list(completer.get_completions(doc, event))
  70. result = sorted(c.text for c in completions)
  71. assert expected == result
  72. # cleanup
  73. shutil.rmtree(test_dir)
  74. def test_pathcompleter_completes_directories_with_only_directories():
  75. # setup: create a test dir with 10 files
  76. test_dir = tempfile.mkdtemp()
  77. write_test_files(test_dir)
  78. # create a sub directory there
  79. os.mkdir(os.path.join(test_dir, "subdir"))
  80. if not test_dir.endswith(os.path.sep):
  81. test_dir += os.path.sep
  82. with chdir(test_dir):
  83. completer = PathCompleter(only_directories=True)
  84. doc_text = ""
  85. doc = Document(doc_text, len(doc_text))
  86. event = CompleteEvent()
  87. completions = list(completer.get_completions(doc, event))
  88. result = [c.text for c in completions]
  89. assert ["subdir"] == result
  90. # check that there is no completion when passing a file
  91. with chdir(test_dir):
  92. completer = PathCompleter(only_directories=True)
  93. doc_text = "1"
  94. doc = Document(doc_text, len(doc_text))
  95. event = CompleteEvent()
  96. completions = list(completer.get_completions(doc, event))
  97. assert [] == completions
  98. # cleanup
  99. shutil.rmtree(test_dir)
  100. def test_pathcompleter_respects_completions_under_min_input_len():
  101. # setup: create a test dir with 10 files
  102. test_dir = tempfile.mkdtemp()
  103. write_test_files(test_dir)
  104. # min len:1 and no text
  105. with chdir(test_dir):
  106. completer = PathCompleter(min_input_len=1)
  107. doc_text = ""
  108. doc = Document(doc_text, len(doc_text))
  109. event = CompleteEvent()
  110. completions = list(completer.get_completions(doc, event))
  111. assert [] == completions
  112. # min len:1 and text of len 1
  113. with chdir(test_dir):
  114. completer = PathCompleter(min_input_len=1)
  115. doc_text = "1"
  116. doc = Document(doc_text, len(doc_text))
  117. event = CompleteEvent()
  118. completions = list(completer.get_completions(doc, event))
  119. result = [c.text for c in completions]
  120. assert [""] == result
  121. # min len:0 and text of len 2
  122. with chdir(test_dir):
  123. completer = PathCompleter(min_input_len=0)
  124. doc_text = "1"
  125. doc = Document(doc_text, len(doc_text))
  126. event = CompleteEvent()
  127. completions = list(completer.get_completions(doc, event))
  128. result = [c.text for c in completions]
  129. assert [""] == result
  130. # create 10 files with a 2 char long name
  131. for i in range(10):
  132. with open(os.path.join(test_dir, str(i) * 2), "wb") as out:
  133. out.write(b"")
  134. # min len:1 and text of len 1
  135. with chdir(test_dir):
  136. completer = PathCompleter(min_input_len=1)
  137. doc_text = "2"
  138. doc = Document(doc_text, len(doc_text))
  139. event = CompleteEvent()
  140. completions = list(completer.get_completions(doc, event))
  141. result = sorted(c.text for c in completions)
  142. assert ["", "2"] == result
  143. # min len:2 and text of len 1
  144. with chdir(test_dir):
  145. completer = PathCompleter(min_input_len=2)
  146. doc_text = "2"
  147. doc = Document(doc_text, len(doc_text))
  148. event = CompleteEvent()
  149. completions = list(completer.get_completions(doc, event))
  150. assert [] == completions
  151. # cleanup
  152. shutil.rmtree(test_dir)
  153. def test_pathcompleter_does_not_expanduser_by_default():
  154. completer = PathCompleter()
  155. doc_text = "~"
  156. doc = Document(doc_text, len(doc_text))
  157. event = CompleteEvent()
  158. completions = list(completer.get_completions(doc, event))
  159. assert [] == completions
  160. def test_pathcompleter_can_expanduser(monkeypatch):
  161. monkeypatch.setenv('HOME', '/tmp')
  162. completer = PathCompleter(expanduser=True)
  163. doc_text = "~"
  164. doc = Document(doc_text, len(doc_text))
  165. event = CompleteEvent()
  166. completions = list(completer.get_completions(doc, event))
  167. assert len(completions) > 0
  168. def test_pathcompleter_can_apply_file_filter():
  169. # setup: create a test dir with 10 files
  170. test_dir = tempfile.mkdtemp()
  171. write_test_files(test_dir)
  172. # add a .csv file
  173. with open(os.path.join(test_dir, "my.csv"), "wb") as out:
  174. out.write(b"")
  175. file_filter = lambda f: f and f.endswith(".csv")
  176. with chdir(test_dir):
  177. completer = PathCompleter(file_filter=file_filter)
  178. doc_text = ""
  179. doc = Document(doc_text, len(doc_text))
  180. event = CompleteEvent()
  181. completions = list(completer.get_completions(doc, event))
  182. result = [c.text for c in completions]
  183. assert ["my.csv"] == result
  184. # cleanup
  185. shutil.rmtree(test_dir)
  186. def test_pathcompleter_get_paths_constrains_path():
  187. # setup: create a test dir with 10 files
  188. test_dir = tempfile.mkdtemp()
  189. write_test_files(test_dir)
  190. # add a subdir with 10 other files with different names
  191. subdir = os.path.join(test_dir, "subdir")
  192. os.mkdir(subdir)
  193. write_test_files(subdir, "abcdefghij")
  194. get_paths = lambda: ["subdir"]
  195. with chdir(test_dir):
  196. completer = PathCompleter(get_paths=get_paths)
  197. doc_text = ""
  198. doc = Document(doc_text, len(doc_text))
  199. event = CompleteEvent()
  200. completions = list(completer.get_completions(doc, event))
  201. result = [c.text for c in completions]
  202. expected = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
  203. assert expected == result
  204. # cleanup
  205. shutil.rmtree(test_dir)
  206. def test_word_completer_static_word_list():
  207. completer = WordCompleter(["abc", "def", "aaa"])
  208. # Static list on empty input.
  209. completions = completer.get_completions(Document(""), CompleteEvent())
  210. assert [c.text for c in completions] == ["abc", "def", "aaa"]
  211. # Static list on non-empty input.
  212. completions = completer.get_completions(Document("a"), CompleteEvent())
  213. assert [c.text for c in completions] == ["abc", "aaa"]
  214. completions = completer.get_completions(Document("A"), CompleteEvent())
  215. assert [c.text for c in completions] == []
  216. # Multiple words ending with space. (Accept all options)
  217. completions = completer.get_completions(Document("test "), CompleteEvent())
  218. assert [c.text for c in completions] == ["abc", "def", "aaa"]
  219. # Multiple words. (Check last only.)
  220. completions = completer.get_completions(Document("test a"), CompleteEvent())
  221. assert [c.text for c in completions] == ["abc", "aaa"]
  222. def test_word_completer_ignore_case():
  223. completer = WordCompleter(["abc", "def", "aaa"], ignore_case=True)
  224. completions = completer.get_completions(Document("a"), CompleteEvent())
  225. assert [c.text for c in completions] == ["abc", "aaa"]
  226. completions = completer.get_completions(Document("A"), CompleteEvent())
  227. assert [c.text for c in completions] == ["abc", "aaa"]
  228. def test_word_completer_match_middle():
  229. completer = WordCompleter(["abc", "def", "abca"], match_middle=True)
  230. completions = completer.get_completions(Document("bc"), CompleteEvent())
  231. assert [c.text for c in completions] == ["abc", "abca"]
  232. def test_word_completer_sentence():
  233. # With sentence=True
  234. completer = WordCompleter(
  235. ["hello world", "www", "hello www", "hello there"], sentence=True
  236. )
  237. completions = completer.get_completions(Document("hello w"), CompleteEvent())
  238. assert [c.text for c in completions] == ["hello world", "hello www"]
  239. # With sentence=False
  240. completer = WordCompleter(
  241. ["hello world", "www", "hello www", "hello there"], sentence=False
  242. )
  243. completions = completer.get_completions(Document("hello w"), CompleteEvent())
  244. assert [c.text for c in completions] == ["www"]
  245. def test_word_completer_dynamic_word_list():
  246. called = [0]
  247. def get_words():
  248. called[0] += 1
  249. return ["abc", "def", "aaa"]
  250. completer = WordCompleter(get_words)
  251. # Dynamic list on empty input.
  252. completions = completer.get_completions(Document(""), CompleteEvent())
  253. assert [c.text for c in completions] == ["abc", "def", "aaa"]
  254. assert called[0] == 1
  255. # Static list on non-empty input.
  256. completions = completer.get_completions(Document("a"), CompleteEvent())
  257. assert [c.text for c in completions] == ["abc", "aaa"]
  258. assert called[0] == 2
  259. def test_word_completer_pattern():
  260. # With a pattern which support '.'
  261. completer = WordCompleter(
  262. ["abc", "a.b.c", "a.b", "xyz"],
  263. pattern=re.compile(r"^([a-zA-Z0-9_.]+|[^a-zA-Z0-9_.\s]+)"),
  264. )
  265. completions = completer.get_completions(Document("a."), CompleteEvent())
  266. assert [c.text for c in completions] == ["a.b.c", "a.b"]
  267. # Without pattern
  268. completer = WordCompleter(["abc", "a.b.c", "a.b", "xyz"])
  269. completions = completer.get_completions(Document("a."), CompleteEvent())
  270. assert [c.text for c in completions] == []
  271. def test_fuzzy_completer():
  272. collection = [
  273. "migrations.py",
  274. "django_migrations.py",
  275. "django_admin_log.py",
  276. "api_user.doc",
  277. "user_group.doc",
  278. "users.txt",
  279. "accounts.txt",
  280. "123.py",
  281. "test123test.py",
  282. ]
  283. completer = FuzzyWordCompleter(collection)
  284. completions = completer.get_completions(Document("txt"), CompleteEvent())
  285. assert [c.text for c in completions] == ["users.txt", "accounts.txt"]
  286. completions = completer.get_completions(Document("djmi"), CompleteEvent())
  287. assert [c.text for c in completions] == [
  288. "django_migrations.py",
  289. "django_admin_log.py",
  290. ]
  291. completions = completer.get_completions(Document("mi"), CompleteEvent())
  292. assert [c.text for c in completions] == [
  293. "migrations.py",
  294. "django_migrations.py",
  295. "django_admin_log.py",
  296. ]
  297. completions = completer.get_completions(Document("user"), CompleteEvent())
  298. assert [c.text for c in completions] == [
  299. "user_group.doc",
  300. "users.txt",
  301. "api_user.doc",
  302. ]
  303. completions = completer.get_completions(Document("123"), CompleteEvent())
  304. assert [c.text for c in completions] == ["123.py", "test123test.py"]
  305. completions = completer.get_completions(Document("miGr"), CompleteEvent())
  306. assert [c.text for c in completions] == [
  307. "migrations.py",
  308. "django_migrations.py",
  309. ]
  310. # Multiple words ending with space. (Accept all options)
  311. completions = completer.get_completions(Document("test "), CompleteEvent())
  312. assert [c.text for c in completions] == collection
  313. # Multiple words. (Check last only.)
  314. completions = completer.get_completions(Document("test txt"), CompleteEvent())
  315. assert [c.text for c in completions] == ["users.txt", "accounts.txt"]
  316. def test_nested_completer():
  317. completer = NestedCompleter.from_nested_dict(
  318. {
  319. "show": {
  320. "version": None,
  321. "clock": None,
  322. "interfaces": None,
  323. "ip": {"interface": {"brief"}},
  324. },
  325. "exit": None,
  326. }
  327. )
  328. # Empty input.
  329. completions = completer.get_completions(Document(""), CompleteEvent())
  330. assert {c.text for c in completions} == {"show", "exit"}
  331. # One character.
  332. completions = completer.get_completions(Document("s"), CompleteEvent())
  333. assert {c.text for c in completions} == {"show"}
  334. # One word.
  335. completions = completer.get_completions(Document("show"), CompleteEvent())
  336. assert {c.text for c in completions} == {"show"}
  337. # One word + space.
  338. completions = completer.get_completions(Document("show "), CompleteEvent())
  339. assert {c.text for c in completions} == {"version", "clock", "interfaces", "ip"}
  340. # One word + space + one character.
  341. completions = completer.get_completions(Document("show i"), CompleteEvent())
  342. assert {c.text for c in completions} == {"ip", "interfaces"}
  343. # One space + one word + space + one character.
  344. completions = completer.get_completions(Document(" show i"), CompleteEvent())
  345. assert {c.text for c in completions} == {"ip", "interfaces"}
  346. # Test nested set.
  347. completions = completer.get_completions(
  348. Document("show ip interface br"), CompleteEvent()
  349. )
  350. assert {c.text for c in completions} == {"brief"}
  351. def test_deduplicate_completer():
  352. def create_completer(deduplicate: bool):
  353. return merge_completers(
  354. [
  355. WordCompleter(["hello", "world", "abc", "def"]),
  356. WordCompleter(["xyz", "xyz", "abc", "def"]),
  357. ],
  358. deduplicate=deduplicate,
  359. )
  360. completions = list(
  361. create_completer(deduplicate=False).get_completions(
  362. Document(""), CompleteEvent()
  363. )
  364. )
  365. assert len(completions) == 8
  366. completions = list(
  367. create_completer(deduplicate=True).get_completions(
  368. Document(""), CompleteEvent()
  369. )
  370. )
  371. assert len(completions) == 5