autocomplete.test.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { describe, expect, test } from "bun:test"
  2. import path from "path"
  3. import type { KeymapCommand } from "@opencode-ai/plugin/tui/context"
  4. import {
  5. directoryAutocompleteExactValue,
  6. directoryAutocompleteMatches,
  7. directoryAutocompleteResultValue,
  8. directoryAutocompleteSearch,
  9. directoryRecentValue,
  10. slashArgumentAutocomplete,
  11. } from "../../src/prompt/directory-completion"
  12. const commands = [
  13. {
  14. id: "session.cd",
  15. slash: { name: "cd", aliases: ["chdir"], arguments: true },
  16. run: () => undefined,
  17. },
  18. ] satisfies KeymapCommand[]
  19. const argumentAutocomplete = (command: KeymapCommand) =>
  20. command.id === "session.cd" ? ("directory" as const) : undefined
  21. describe("slashArgumentAutocomplete", () => {
  22. test("starts after the command separator", () => {
  23. expect(slashArgumentAutocomplete("/cd ", 4, commands, argumentAutocomplete)).toEqual({
  24. type: "directory",
  25. index: 4,
  26. })
  27. expect(slashArgumentAutocomplete("/cd src", 7, commands, argumentAutocomplete)).toEqual({
  28. type: "directory",
  29. index: 4,
  30. })
  31. })
  32. test("supports aliases", () => {
  33. expect(slashArgumentAutocomplete("/chdir src", 10, commands, argumentAutocomplete)).toEqual({
  34. type: "directory",
  35. index: 7,
  36. })
  37. })
  38. test("does not complete the command token", () => {
  39. expect(slashArgumentAutocomplete("/cd", 3, commands, argumentAutocomplete)).toBeUndefined()
  40. expect(slashArgumentAutocomplete("/other ", 7, commands, argumentAutocomplete)).toBeUndefined()
  41. })
  42. })
  43. describe("directoryAutocompleteSearch", () => {
  44. test("searches from home after a home prefix", () => {
  45. expect(directoryAutocompleteSearch("~", "/project", "/home/user")).toEqual({
  46. directory: "/home/user",
  47. prefix: "~/",
  48. query: "",
  49. })
  50. expect(directoryAutocompleteSearch("~/pro", "/project", "/home/user")).toEqual({
  51. directory: "/home/user",
  52. prefix: "~/",
  53. query: "pro",
  54. })
  55. expect(directoryAutocompleteSearch("~/projects/open", "/project", "/home/user")).toEqual({
  56. // The implementation resolves subdirectories with the platform path
  57. // module, so expectations resolve too (drive-prefixed on Windows).
  58. directory: path.resolve("/home/user/projects"),
  59. prefix: "~/projects/",
  60. query: "open",
  61. })
  62. })
  63. test("searches from parent prefixes", () => {
  64. expect(directoryAutocompleteSearch("..", "/project/src", "/home/user")).toEqual({
  65. directory: path.resolve("/project"),
  66. prefix: "../",
  67. query: "",
  68. })
  69. expect(directoryAutocompleteSearch("../../pac", "/project/src/lib", "/home/user")).toEqual({
  70. directory: path.resolve("/project"),
  71. prefix: "../../",
  72. query: "pac",
  73. })
  74. expect(directoryAutocompleteSearch("../../..", "/project/src/lib", "/home/user")).toEqual({
  75. directory: path.resolve("/"),
  76. prefix: "../../../",
  77. query: "",
  78. })
  79. })
  80. test("keeps ordinary searches rooted at the current directory", () => {
  81. expect(directoryAutocompleteSearch("src", "/project", "/home/user")).toEqual({
  82. directory: "/project",
  83. prefix: "",
  84. query: "src",
  85. })
  86. expect(directoryAutocompleteSearch("packages/core", "/project", "/home/user")).toEqual({
  87. directory: path.resolve("/project/packages"),
  88. prefix: "packages/",
  89. query: "core",
  90. })
  91. expect(directoryAutocompleteSearch("/root/pro", "/project", "/home/user")).toEqual({
  92. directory: path.resolve("/root"),
  93. prefix: "/root/",
  94. query: "pro",
  95. })
  96. })
  97. })
  98. describe("directoryAutocompleteResultValue", () => {
  99. test("preserves explicit roots", () => {
  100. expect(
  101. directoryAutocompleteResultValue("projects/", directoryAutocompleteSearch("~/", "/project", "/home/user")),
  102. ).toBe("~/projects/")
  103. expect(
  104. directoryAutocompleteResultValue("src/", directoryAutocompleteSearch("../", "/project/pkg", "/home/user")),
  105. ).toBe("../src/")
  106. })
  107. })
  108. describe("directoryAutocompleteExactValue", () => {
  109. test("includes complete explicit roots", () => {
  110. expect(
  111. directoryAutocompleteExactValue("../..", directoryAutocompleteSearch("../..", "/project/pkg", "/home/user")),
  112. ).toBe("../..")
  113. expect(directoryAutocompleteExactValue("~", directoryAutocompleteSearch("~", "/project", "/home/user"))).toBe("~")
  114. })
  115. test("omits incomplete and implicit roots", () => {
  116. expect(
  117. directoryAutocompleteExactValue("../../src", directoryAutocompleteSearch("../../src", "/project", "/home/user")),
  118. ).toBeUndefined()
  119. expect(
  120. directoryAutocompleteExactValue("", directoryAutocompleteSearch("", "/project", "/home/user")),
  121. ).toBeUndefined()
  122. })
  123. })
  124. describe("directoryAutocompleteMatches", () => {
  125. test("hides dot directories for an empty component", () => {
  126. expect(directoryAutocompleteMatches("src/", "")).toBe(true)
  127. expect(directoryAutocompleteMatches(".git/", "")).toBe(false)
  128. })
  129. test("shows dot directories when explicitly filtered", () => {
  130. expect(directoryAutocompleteMatches(".git/", ".")).toBe(true)
  131. expect(directoryAutocompleteMatches(".github/", ".gi")).toBe(true)
  132. expect(directoryAutocompleteMatches(".zed/", ".gi")).toBe(false)
  133. })
  134. })
  135. describe("directoryRecentValue", () => {
  136. test("abbreviates home paths", () => {
  137. expect(directoryRecentValue("/home/user", "/home/user")).toBe("~")
  138. expect(directoryRecentValue("/home/user/projects/opencode", "/home/user")).toBe("~/projects/opencode")
  139. })
  140. test("keeps paths outside home absolute", () => {
  141. expect(directoryRecentValue("/project/recent", "/home/user")).toBe("/project/recent")
  142. })
  143. })