@smthrs/std 0.0.0-stage → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/LICENSE +21 -0
  3. package/README.md +152 -2
  4. package/dist/cjs/ApplyPatch.d.ts +174 -0
  5. package/dist/cjs/ApplyPatch.d.ts.map +1 -0
  6. package/dist/cjs/ApplyPatch.js +309 -0
  7. package/dist/cjs/ApplyPatch.js.map +7 -0
  8. package/dist/cjs/Bash.d.ts +380 -0
  9. package/dist/cjs/Bash.d.ts.map +1 -0
  10. package/dist/cjs/Bash.js +332 -0
  11. package/dist/cjs/Bash.js.map +7 -0
  12. package/dist/cjs/Checkpoints.d.ts +279 -0
  13. package/dist/cjs/Checkpoints.d.ts.map +1 -0
  14. package/dist/cjs/Checkpoints.js +145 -0
  15. package/dist/cjs/Checkpoints.js.map +7 -0
  16. package/dist/cjs/Container.d.ts +132 -0
  17. package/dist/cjs/Container.d.ts.map +1 -0
  18. package/dist/cjs/Container.js +113 -0
  19. package/dist/cjs/Container.js.map +7 -0
  20. package/dist/cjs/Edit.d.ts +203 -0
  21. package/dist/cjs/Edit.d.ts.map +1 -0
  22. package/dist/cjs/Edit.js +258 -0
  23. package/dist/cjs/Edit.js.map +7 -0
  24. package/dist/cjs/ExaWebSearch.d.ts +22 -0
  25. package/dist/cjs/ExaWebSearch.d.ts.map +1 -0
  26. package/dist/cjs/ExaWebSearch.js +138 -0
  27. package/dist/cjs/ExaWebSearch.js.map +7 -0
  28. package/dist/cjs/Explore.d.ts +328 -0
  29. package/dist/cjs/Explore.d.ts.map +1 -0
  30. package/dist/cjs/Explore.js +90 -0
  31. package/dist/cjs/Explore.js.map +7 -0
  32. package/dist/cjs/Fetch.d.ts +130 -0
  33. package/dist/cjs/Fetch.d.ts.map +1 -0
  34. package/dist/cjs/Fetch.js +64 -0
  35. package/dist/cjs/Fetch.js.map +7 -0
  36. package/dist/cjs/Glob.d.ts +204 -0
  37. package/dist/cjs/Glob.d.ts.map +1 -0
  38. package/dist/cjs/Glob.js +94 -0
  39. package/dist/cjs/Glob.js.map +7 -0
  40. package/dist/cjs/Grep.d.ts +347 -0
  41. package/dist/cjs/Grep.d.ts.map +1 -0
  42. package/dist/cjs/Grep.js +181 -0
  43. package/dist/cjs/Grep.js.map +7 -0
  44. package/dist/cjs/HttpPost.d.ts +130 -0
  45. package/dist/cjs/HttpPost.d.ts.map +1 -0
  46. package/dist/cjs/HttpPost.js +72 -0
  47. package/dist/cjs/HttpPost.js.map +7 -0
  48. package/dist/cjs/LanguageServer.d.ts +90 -0
  49. package/dist/cjs/LanguageServer.d.ts.map +1 -0
  50. package/dist/cjs/LanguageServer.js +60 -0
  51. package/dist/cjs/LanguageServer.js.map +7 -0
  52. package/dist/cjs/Ls.d.ts +164 -0
  53. package/dist/cjs/Ls.d.ts.map +1 -0
  54. package/dist/cjs/Ls.js +136 -0
  55. package/dist/cjs/Ls.js.map +7 -0
  56. package/dist/cjs/Lsp.d.ts +125 -0
  57. package/dist/cjs/Lsp.d.ts.map +1 -0
  58. package/dist/cjs/Lsp.js +137 -0
  59. package/dist/cjs/Lsp.js.map +7 -0
  60. package/dist/cjs/Manifest.d.ts +918 -0
  61. package/dist/cjs/Manifest.d.ts.map +1 -0
  62. package/dist/cjs/Manifest.js +144 -0
  63. package/dist/cjs/Manifest.js.map +7 -0
  64. package/dist/cjs/NativeSearch.d.ts +38 -0
  65. package/dist/cjs/NativeSearch.d.ts.map +1 -0
  66. package/dist/cjs/NativeSearch.js +326 -0
  67. package/dist/cjs/NativeSearch.js.map +7 -0
  68. package/dist/cjs/NodeLanguageServer.d.ts +128 -0
  69. package/dist/cjs/NodeLanguageServer.d.ts.map +1 -0
  70. package/dist/cjs/NodeLanguageServer.js +796 -0
  71. package/dist/cjs/NodeLanguageServer.js.map +7 -0
  72. package/dist/cjs/PortableSearch.d.ts +24 -0
  73. package/dist/cjs/PortableSearch.d.ts.map +1 -0
  74. package/dist/cjs/PortableSearch.js +233 -0
  75. package/dist/cjs/PortableSearch.js.map +7 -0
  76. package/dist/cjs/Probe.d.ts +193 -0
  77. package/dist/cjs/Probe.d.ts.map +1 -0
  78. package/dist/cjs/Probe.js +134 -0
  79. package/dist/cjs/Probe.js.map +7 -0
  80. package/dist/cjs/Read.d.ts +189 -0
  81. package/dist/cjs/Read.d.ts.map +1 -0
  82. package/dist/cjs/Read.js +223 -0
  83. package/dist/cjs/Read.js.map +7 -0
  84. package/dist/cjs/Relocate.d.ts +73 -0
  85. package/dist/cjs/Relocate.d.ts.map +1 -0
  86. package/dist/cjs/Relocate.js +72 -0
  87. package/dist/cjs/Relocate.js.map +7 -0
  88. package/dist/cjs/Search.d.ts +163 -0
  89. package/dist/cjs/Search.d.ts.map +1 -0
  90. package/dist/cjs/Search.js +51 -0
  91. package/dist/cjs/Search.js.map +7 -0
  92. package/dist/cjs/SearchConformance.d.ts +115 -0
  93. package/dist/cjs/SearchConformance.d.ts.map +1 -0
  94. package/dist/cjs/SearchConformance.js +172 -0
  95. package/dist/cjs/SearchConformance.js.map +7 -0
  96. package/dist/cjs/SearchContract.d.ts +114 -0
  97. package/dist/cjs/SearchContract.d.ts.map +1 -0
  98. package/dist/cjs/SearchContract.js +339 -0
  99. package/dist/cjs/SearchContract.js.map +7 -0
  100. package/dist/cjs/ShellCommand.d.ts +164 -0
  101. package/dist/cjs/ShellCommand.d.ts.map +1 -0
  102. package/dist/cjs/ShellCommand.js +102 -0
  103. package/dist/cjs/ShellCommand.js.map +7 -0
  104. package/dist/cjs/StdError.d.ts +55 -0
  105. package/dist/cjs/StdError.d.ts.map +1 -0
  106. package/dist/cjs/StdError.js +74 -0
  107. package/dist/cjs/StdError.js.map +7 -0
  108. package/dist/cjs/TestRun.d.ts +311 -0
  109. package/dist/cjs/TestRun.d.ts.map +1 -0
  110. package/dist/cjs/TestRun.js +228 -0
  111. package/dist/cjs/TestRun.js.map +7 -0
  112. package/dist/cjs/TestRunner.d.ts +102 -0
  113. package/dist/cjs/TestRunner.d.ts.map +1 -0
  114. package/dist/cjs/TestRunner.js +54 -0
  115. package/dist/cjs/TestRunner.js.map +7 -0
  116. package/dist/cjs/TreeFingerprint.d.ts +140 -0
  117. package/dist/cjs/TreeFingerprint.d.ts.map +1 -0
  118. package/dist/cjs/TreeFingerprint.js +87 -0
  119. package/dist/cjs/TreeFingerprint.js.map +7 -0
  120. package/dist/cjs/UpdatePlan.d.ts +152 -0
  121. package/dist/cjs/UpdatePlan.d.ts.map +1 -0
  122. package/dist/cjs/UpdatePlan.js +87 -0
  123. package/dist/cjs/UpdatePlan.js.map +7 -0
  124. package/dist/cjs/WebFetch.d.ts +134 -0
  125. package/dist/cjs/WebFetch.d.ts.map +1 -0
  126. package/dist/cjs/WebFetch.js +140 -0
  127. package/dist/cjs/WebFetch.js.map +7 -0
  128. package/dist/cjs/WebSearch.d.ts +181 -0
  129. package/dist/cjs/WebSearch.d.ts.map +1 -0
  130. package/dist/cjs/WebSearch.js +86 -0
  131. package/dist/cjs/WebSearch.js.map +7 -0
  132. package/dist/cjs/Write.d.ts +147 -0
  133. package/dist/cjs/Write.d.ts.map +1 -0
  134. package/dist/cjs/Write.js +122 -0
  135. package/dist/cjs/Write.js.map +7 -0
  136. package/dist/cjs/index.d.ts +127 -0
  137. package/dist/cjs/index.d.ts.map +1 -0
  138. package/dist/cjs/index.js +99 -0
  139. package/dist/cjs/index.js.map +7 -0
  140. package/dist/cjs/internal/ApplyPatch.d.ts +145 -0
  141. package/dist/cjs/internal/ApplyPatch.d.ts.map +1 -0
  142. package/dist/cjs/internal/ApplyPatch.js +462 -0
  143. package/dist/cjs/internal/ApplyPatch.js.map +7 -0
  144. package/dist/cjs/internal/CodexText.d.ts +73 -0
  145. package/dist/cjs/internal/CodexText.d.ts.map +1 -0
  146. package/dist/cjs/internal/CodexText.js +108 -0
  147. package/dist/cjs/internal/CodexText.js.map +7 -0
  148. package/dist/cjs/internal/Declaration.d.ts +49 -0
  149. package/dist/cjs/internal/Declaration.d.ts.map +1 -0
  150. package/dist/cjs/internal/Declaration.js +46 -0
  151. package/dist/cjs/internal/Declaration.js.map +7 -0
  152. package/dist/cjs/internal/Diagnostics.d.ts +77 -0
  153. package/dist/cjs/internal/Diagnostics.d.ts.map +1 -0
  154. package/dist/cjs/internal/Diagnostics.js +89 -0
  155. package/dist/cjs/internal/Diagnostics.js.map +7 -0
  156. package/dist/cjs/internal/EnvelopePrecheck.d.ts +67 -0
  157. package/dist/cjs/internal/EnvelopePrecheck.d.ts.map +1 -0
  158. package/dist/cjs/internal/EnvelopePrecheck.js +124 -0
  159. package/dist/cjs/internal/EnvelopePrecheck.js.map +7 -0
  160. package/dist/cjs/internal/Exec.d.ts +139 -0
  161. package/dist/cjs/internal/Exec.d.ts.map +1 -0
  162. package/dist/cjs/internal/Exec.js +158 -0
  163. package/dist/cjs/internal/Exec.js.map +7 -0
  164. package/dist/cjs/internal/FileMutation.d.ts +109 -0
  165. package/dist/cjs/internal/FileMutation.d.ts.map +1 -0
  166. package/dist/cjs/internal/FileMutation.js +116 -0
  167. package/dist/cjs/internal/FileMutation.js.map +7 -0
  168. package/dist/cjs/internal/FsFailure.d.ts +38 -0
  169. package/dist/cjs/internal/FsFailure.d.ts.map +1 -0
  170. package/dist/cjs/internal/FsFailure.js +40 -0
  171. package/dist/cjs/internal/FsFailure.js.map +7 -0
  172. package/dist/cjs/internal/GitWorktree.d.ts +108 -0
  173. package/dist/cjs/internal/GitWorktree.d.ts.map +1 -0
  174. package/dist/cjs/internal/GitWorktree.js +276 -0
  175. package/dist/cjs/internal/GitWorktree.js.map +7 -0
  176. package/dist/cjs/internal/Grouping.d.ts +51 -0
  177. package/dist/cjs/internal/Grouping.d.ts.map +1 -0
  178. package/dist/cjs/internal/Grouping.js +83 -0
  179. package/dist/cjs/internal/Grouping.js.map +7 -0
  180. package/dist/cjs/internal/Html.d.ts +20 -0
  181. package/dist/cjs/internal/Html.d.ts.map +1 -0
  182. package/dist/cjs/internal/Html.js +81 -0
  183. package/dist/cjs/internal/Html.js.map +7 -0
  184. package/dist/cjs/internal/Http.d.ts +95 -0
  185. package/dist/cjs/internal/Http.d.ts.map +1 -0
  186. package/dist/cjs/internal/Http.js +153 -0
  187. package/dist/cjs/internal/Http.js.map +7 -0
  188. package/dist/cjs/internal/HttpNetwork.d.ts +27 -0
  189. package/dist/cjs/internal/HttpNetwork.d.ts.map +1 -0
  190. package/dist/cjs/internal/HttpNetwork.js +146 -0
  191. package/dist/cjs/internal/HttpNetwork.js.map +7 -0
  192. package/dist/cjs/internal/Ignore.d.ts +37 -0
  193. package/dist/cjs/internal/Ignore.d.ts.map +1 -0
  194. package/dist/cjs/internal/Ignore.js +100 -0
  195. package/dist/cjs/internal/Ignore.js.map +7 -0
  196. package/dist/cjs/internal/LinearRegex.d.ts +19 -0
  197. package/dist/cjs/internal/LinearRegex.d.ts.map +1 -0
  198. package/dist/cjs/internal/LinearRegex.js +166 -0
  199. package/dist/cjs/internal/LinearRegex.js.map +7 -0
  200. package/dist/cjs/internal/Match.d.ts +85 -0
  201. package/dist/cjs/internal/Match.d.ts.map +1 -0
  202. package/dist/cjs/internal/Match.js +99 -0
  203. package/dist/cjs/internal/Match.js.map +7 -0
  204. package/dist/cjs/internal/Paths.d.ts +23 -0
  205. package/dist/cjs/internal/Paths.d.ts.map +1 -0
  206. package/dist/cjs/internal/Paths.js +52 -0
  207. package/dist/cjs/internal/Paths.js.map +7 -0
  208. package/dist/cjs/internal/Preserve.d.ts +25 -0
  209. package/dist/cjs/internal/Preserve.d.ts.map +1 -0
  210. package/dist/cjs/internal/Preserve.js +88 -0
  211. package/dist/cjs/internal/Preserve.js.map +7 -0
  212. package/dist/cjs/internal/Prune.d.ts +35 -0
  213. package/dist/cjs/internal/Prune.d.ts.map +1 -0
  214. package/dist/cjs/internal/Prune.js +54 -0
  215. package/dist/cjs/internal/Prune.js.map +7 -0
  216. package/dist/cjs/internal/SearchContract.d.ts +63 -0
  217. package/dist/cjs/internal/SearchContract.d.ts.map +1 -0
  218. package/dist/cjs/internal/SearchContract.js +46 -0
  219. package/dist/cjs/internal/SearchContract.js.map +7 -0
  220. package/dist/cjs/internal/Symbols.d.ts +39 -0
  221. package/dist/cjs/internal/Symbols.d.ts.map +1 -0
  222. package/dist/cjs/internal/Symbols.js +59 -0
  223. package/dist/cjs/internal/Symbols.js.map +7 -0
  224. package/dist/cjs/internal/TestReport.d.ts +52 -0
  225. package/dist/cjs/internal/TestReport.d.ts.map +1 -0
  226. package/dist/cjs/internal/TestReport.js +131 -0
  227. package/dist/cjs/internal/TestReport.js.map +7 -0
  228. package/dist/cjs/internal/Text.d.ts +114 -0
  229. package/dist/cjs/internal/Text.d.ts.map +1 -0
  230. package/dist/cjs/internal/Text.js +82 -0
  231. package/dist/cjs/internal/Text.js.map +7 -0
  232. package/dist/cjs/internal/Url.d.ts +13 -0
  233. package/dist/cjs/internal/Url.d.ts.map +1 -0
  234. package/dist/cjs/internal/Url.js +35 -0
  235. package/dist/cjs/internal/Url.js.map +7 -0
  236. package/dist/cjs/internal/Walk.d.ts +72 -0
  237. package/dist/cjs/internal/Walk.d.ts.map +1 -0
  238. package/dist/cjs/internal/Walk.js +108 -0
  239. package/dist/cjs/internal/Walk.js.map +7 -0
  240. package/dist/cjs/package.json +1 -0
  241. package/dist/esm/ApplyPatch.d.ts +174 -0
  242. package/dist/esm/ApplyPatch.d.ts.map +1 -0
  243. package/dist/esm/ApplyPatch.js +346 -0
  244. package/dist/esm/ApplyPatch.js.map +1 -0
  245. package/dist/esm/Bash.d.ts +380 -0
  246. package/dist/esm/Bash.d.ts.map +1 -0
  247. package/dist/esm/Bash.js +505 -0
  248. package/dist/esm/Bash.js.map +1 -0
  249. package/dist/esm/Checkpoints.d.ts +279 -0
  250. package/dist/esm/Checkpoints.d.ts.map +1 -0
  251. package/dist/esm/Checkpoints.js +298 -0
  252. package/dist/esm/Checkpoints.js.map +1 -0
  253. package/dist/esm/Container.d.ts +132 -0
  254. package/dist/esm/Container.d.ts.map +1 -0
  255. package/dist/esm/Container.js +151 -0
  256. package/dist/esm/Container.js.map +1 -0
  257. package/dist/esm/Edit.d.ts +203 -0
  258. package/dist/esm/Edit.d.ts.map +1 -0
  259. package/dist/esm/Edit.js +296 -0
  260. package/dist/esm/Edit.js.map +1 -0
  261. package/dist/esm/ExaWebSearch.d.ts +22 -0
  262. package/dist/esm/ExaWebSearch.d.ts.map +1 -0
  263. package/dist/esm/ExaWebSearch.js +107 -0
  264. package/dist/esm/ExaWebSearch.js.map +1 -0
  265. package/dist/esm/Explore.d.ts +328 -0
  266. package/dist/esm/Explore.d.ts.map +1 -0
  267. package/dist/esm/Explore.js +106 -0
  268. package/dist/esm/Explore.js.map +1 -0
  269. package/dist/esm/Fetch.d.ts +130 -0
  270. package/dist/esm/Fetch.d.ts.map +1 -0
  271. package/dist/esm/Fetch.js +91 -0
  272. package/dist/esm/Fetch.js.map +1 -0
  273. package/dist/esm/Glob.d.ts +204 -0
  274. package/dist/esm/Glob.d.ts.map +1 -0
  275. package/dist/esm/Glob.js +177 -0
  276. package/dist/esm/Glob.js.map +1 -0
  277. package/dist/esm/Grep.d.ts +347 -0
  278. package/dist/esm/Grep.d.ts.map +1 -0
  279. package/dist/esm/Grep.js +289 -0
  280. package/dist/esm/Grep.js.map +1 -0
  281. package/dist/esm/HttpPost.d.ts +130 -0
  282. package/dist/esm/HttpPost.d.ts.map +1 -0
  283. package/dist/esm/HttpPost.js +89 -0
  284. package/dist/esm/HttpPost.js.map +1 -0
  285. package/dist/esm/LanguageServer.d.ts +90 -0
  286. package/dist/esm/LanguageServer.d.ts.map +1 -0
  287. package/dist/esm/LanguageServer.js +57 -0
  288. package/dist/esm/LanguageServer.js.map +1 -0
  289. package/dist/esm/Ls.d.ts +164 -0
  290. package/dist/esm/Ls.d.ts.map +1 -0
  291. package/dist/esm/Ls.js +178 -0
  292. package/dist/esm/Ls.js.map +1 -0
  293. package/dist/esm/Lsp.d.ts +125 -0
  294. package/dist/esm/Lsp.d.ts.map +1 -0
  295. package/dist/esm/Lsp.js +165 -0
  296. package/dist/esm/Lsp.js.map +1 -0
  297. package/dist/esm/Manifest.d.ts +918 -0
  298. package/dist/esm/Manifest.d.ts.map +1 -0
  299. package/dist/esm/Manifest.js +153 -0
  300. package/dist/esm/Manifest.js.map +1 -0
  301. package/dist/esm/NativeSearch.d.ts +38 -0
  302. package/dist/esm/NativeSearch.d.ts.map +1 -0
  303. package/dist/esm/NativeSearch.js +378 -0
  304. package/dist/esm/NativeSearch.js.map +1 -0
  305. package/dist/esm/NodeLanguageServer.d.ts +128 -0
  306. package/dist/esm/NodeLanguageServer.d.ts.map +1 -0
  307. package/dist/esm/NodeLanguageServer.js +843 -0
  308. package/dist/esm/NodeLanguageServer.js.map +1 -0
  309. package/dist/esm/PortableSearch.d.ts +24 -0
  310. package/dist/esm/PortableSearch.d.ts.map +1 -0
  311. package/dist/esm/PortableSearch.js +240 -0
  312. package/dist/esm/PortableSearch.js.map +1 -0
  313. package/dist/esm/Probe.d.ts +193 -0
  314. package/dist/esm/Probe.d.ts.map +1 -0
  315. package/dist/esm/Probe.js +225 -0
  316. package/dist/esm/Probe.js.map +1 -0
  317. package/dist/esm/Read.d.ts +189 -0
  318. package/dist/esm/Read.d.ts.map +1 -0
  319. package/dist/esm/Read.js +300 -0
  320. package/dist/esm/Read.js.map +1 -0
  321. package/dist/esm/Relocate.d.ts +73 -0
  322. package/dist/esm/Relocate.d.ts.map +1 -0
  323. package/dist/esm/Relocate.js +153 -0
  324. package/dist/esm/Relocate.js.map +1 -0
  325. package/dist/esm/Search.d.ts +163 -0
  326. package/dist/esm/Search.d.ts.map +1 -0
  327. package/dist/esm/Search.js +47 -0
  328. package/dist/esm/Search.js.map +1 -0
  329. package/dist/esm/SearchConformance.d.ts +115 -0
  330. package/dist/esm/SearchConformance.d.ts.map +1 -0
  331. package/dist/esm/SearchConformance.js +193 -0
  332. package/dist/esm/SearchConformance.js.map +1 -0
  333. package/dist/esm/SearchContract.d.ts +114 -0
  334. package/dist/esm/SearchContract.d.ts.map +1 -0
  335. package/dist/esm/SearchContract.js +461 -0
  336. package/dist/esm/SearchContract.js.map +1 -0
  337. package/dist/esm/ShellCommand.d.ts +164 -0
  338. package/dist/esm/ShellCommand.d.ts.map +1 -0
  339. package/dist/esm/ShellCommand.js +159 -0
  340. package/dist/esm/ShellCommand.js.map +1 -0
  341. package/dist/esm/StdError.d.ts +55 -0
  342. package/dist/esm/StdError.d.ts.map +1 -0
  343. package/dist/esm/StdError.js +71 -0
  344. package/dist/esm/StdError.js.map +1 -0
  345. package/dist/esm/TestRun.d.ts +311 -0
  346. package/dist/esm/TestRun.d.ts.map +1 -0
  347. package/dist/esm/TestRun.js +316 -0
  348. package/dist/esm/TestRun.js.map +1 -0
  349. package/dist/esm/TestRunner.d.ts +102 -0
  350. package/dist/esm/TestRunner.d.ts.map +1 -0
  351. package/dist/esm/TestRunner.js +68 -0
  352. package/dist/esm/TestRunner.js.map +1 -0
  353. package/dist/esm/TreeFingerprint.d.ts +140 -0
  354. package/dist/esm/TreeFingerprint.d.ts.map +1 -0
  355. package/dist/esm/TreeFingerprint.js +170 -0
  356. package/dist/esm/TreeFingerprint.js.map +1 -0
  357. package/dist/esm/UpdatePlan.d.ts +152 -0
  358. package/dist/esm/UpdatePlan.d.ts.map +1 -0
  359. package/dist/esm/UpdatePlan.js +129 -0
  360. package/dist/esm/UpdatePlan.js.map +1 -0
  361. package/dist/esm/WebFetch.d.ts +134 -0
  362. package/dist/esm/WebFetch.d.ts.map +1 -0
  363. package/dist/esm/WebFetch.js +151 -0
  364. package/dist/esm/WebFetch.js.map +1 -0
  365. package/dist/esm/WebSearch.d.ts +181 -0
  366. package/dist/esm/WebSearch.d.ts.map +1 -0
  367. package/dist/esm/WebSearch.js +134 -0
  368. package/dist/esm/WebSearch.js.map +1 -0
  369. package/dist/esm/Write.d.ts +147 -0
  370. package/dist/esm/Write.d.ts.map +1 -0
  371. package/dist/esm/Write.js +144 -0
  372. package/dist/esm/Write.js.map +1 -0
  373. package/dist/esm/index.d.ts +127 -0
  374. package/dist/esm/index.d.ts.map +1 -0
  375. package/dist/esm/index.js +127 -0
  376. package/dist/esm/index.js.map +1 -0
  377. package/dist/esm/internal/ApplyPatch.d.ts +145 -0
  378. package/dist/esm/internal/ApplyPatch.d.ts.map +1 -0
  379. package/dist/esm/internal/ApplyPatch.js +489 -0
  380. package/dist/esm/internal/ApplyPatch.js.map +1 -0
  381. package/dist/esm/internal/CodexText.d.ts +73 -0
  382. package/dist/esm/internal/CodexText.d.ts.map +1 -0
  383. package/dist/esm/internal/CodexText.js +156 -0
  384. package/dist/esm/internal/CodexText.js.map +1 -0
  385. package/dist/esm/internal/Declaration.d.ts +49 -0
  386. package/dist/esm/internal/Declaration.d.ts.map +1 -0
  387. package/dist/esm/internal/Declaration.js +42 -0
  388. package/dist/esm/internal/Declaration.js.map +1 -0
  389. package/dist/esm/internal/Diagnostics.d.ts +77 -0
  390. package/dist/esm/internal/Diagnostics.d.ts.map +1 -0
  391. package/dist/esm/internal/Diagnostics.js +99 -0
  392. package/dist/esm/internal/Diagnostics.js.map +1 -0
  393. package/dist/esm/internal/EnvelopePrecheck.d.ts +67 -0
  394. package/dist/esm/internal/EnvelopePrecheck.d.ts.map +1 -0
  395. package/dist/esm/internal/EnvelopePrecheck.js +167 -0
  396. package/dist/esm/internal/EnvelopePrecheck.js.map +1 -0
  397. package/dist/esm/internal/Exec.d.ts +139 -0
  398. package/dist/esm/internal/Exec.d.ts.map +1 -0
  399. package/dist/esm/internal/Exec.js +193 -0
  400. package/dist/esm/internal/Exec.js.map +1 -0
  401. package/dist/esm/internal/FileMutation.d.ts +109 -0
  402. package/dist/esm/internal/FileMutation.d.ts.map +1 -0
  403. package/dist/esm/internal/FileMutation.js +123 -0
  404. package/dist/esm/internal/FileMutation.js.map +1 -0
  405. package/dist/esm/internal/FsFailure.d.ts +38 -0
  406. package/dist/esm/internal/FsFailure.d.ts.map +1 -0
  407. package/dist/esm/internal/FsFailure.js +39 -0
  408. package/dist/esm/internal/FsFailure.js.map +1 -0
  409. package/dist/esm/internal/GitWorktree.d.ts +108 -0
  410. package/dist/esm/internal/GitWorktree.d.ts.map +1 -0
  411. package/dist/esm/internal/GitWorktree.js +352 -0
  412. package/dist/esm/internal/GitWorktree.js.map +1 -0
  413. package/dist/esm/internal/Grouping.d.ts +51 -0
  414. package/dist/esm/internal/Grouping.d.ts.map +1 -0
  415. package/dist/esm/internal/Grouping.js +99 -0
  416. package/dist/esm/internal/Grouping.js.map +1 -0
  417. package/dist/esm/internal/Html.d.ts +20 -0
  418. package/dist/esm/internal/Html.d.ts.map +1 -0
  419. package/dist/esm/internal/Html.js +70 -0
  420. package/dist/esm/internal/Html.js.map +1 -0
  421. package/dist/esm/internal/Http.d.ts +95 -0
  422. package/dist/esm/internal/Http.d.ts.map +1 -0
  423. package/dist/esm/internal/Http.js +154 -0
  424. package/dist/esm/internal/Http.js.map +1 -0
  425. package/dist/esm/internal/HttpNetwork.d.ts +27 -0
  426. package/dist/esm/internal/HttpNetwork.d.ts.map +1 -0
  427. package/dist/esm/internal/HttpNetwork.js +114 -0
  428. package/dist/esm/internal/HttpNetwork.js.map +1 -0
  429. package/dist/esm/internal/Ignore.d.ts +37 -0
  430. package/dist/esm/internal/Ignore.d.ts.map +1 -0
  431. package/dist/esm/internal/Ignore.js +128 -0
  432. package/dist/esm/internal/Ignore.js.map +1 -0
  433. package/dist/esm/internal/LinearRegex.d.ts +19 -0
  434. package/dist/esm/internal/LinearRegex.d.ts.map +1 -0
  435. package/dist/esm/internal/LinearRegex.js +176 -0
  436. package/dist/esm/internal/LinearRegex.js.map +1 -0
  437. package/dist/esm/internal/Match.d.ts +85 -0
  438. package/dist/esm/internal/Match.d.ts.map +1 -0
  439. package/dist/esm/internal/Match.js +142 -0
  440. package/dist/esm/internal/Match.js.map +1 -0
  441. package/dist/esm/internal/Paths.d.ts +23 -0
  442. package/dist/esm/internal/Paths.d.ts.map +1 -0
  443. package/dist/esm/internal/Paths.js +40 -0
  444. package/dist/esm/internal/Paths.js.map +1 -0
  445. package/dist/esm/internal/Preserve.d.ts +25 -0
  446. package/dist/esm/internal/Preserve.d.ts.map +1 -0
  447. package/dist/esm/internal/Preserve.js +70 -0
  448. package/dist/esm/internal/Preserve.js.map +1 -0
  449. package/dist/esm/internal/Prune.d.ts +35 -0
  450. package/dist/esm/internal/Prune.d.ts.map +1 -0
  451. package/dist/esm/internal/Prune.js +60 -0
  452. package/dist/esm/internal/Prune.js.map +1 -0
  453. package/dist/esm/internal/SearchContract.d.ts +63 -0
  454. package/dist/esm/internal/SearchContract.d.ts.map +1 -0
  455. package/dist/esm/internal/SearchContract.js +66 -0
  456. package/dist/esm/internal/SearchContract.js.map +1 -0
  457. package/dist/esm/internal/Symbols.d.ts +39 -0
  458. package/dist/esm/internal/Symbols.d.ts.map +1 -0
  459. package/dist/esm/internal/Symbols.js +79 -0
  460. package/dist/esm/internal/Symbols.js.map +1 -0
  461. package/dist/esm/internal/TestReport.d.ts +52 -0
  462. package/dist/esm/internal/TestReport.d.ts.map +1 -0
  463. package/dist/esm/internal/TestReport.js +172 -0
  464. package/dist/esm/internal/TestReport.js.map +1 -0
  465. package/dist/esm/internal/Text.d.ts +114 -0
  466. package/dist/esm/internal/Text.d.ts.map +1 -0
  467. package/dist/esm/internal/Text.js +130 -0
  468. package/dist/esm/internal/Text.js.map +1 -0
  469. package/dist/esm/internal/Url.d.ts +13 -0
  470. package/dist/esm/internal/Url.d.ts.map +1 -0
  471. package/dist/esm/internal/Url.js +27 -0
  472. package/dist/esm/internal/Url.js.map +1 -0
  473. package/dist/esm/internal/Walk.d.ts +72 -0
  474. package/dist/esm/internal/Walk.d.ts.map +1 -0
  475. package/dist/esm/internal/Walk.js +126 -0
  476. package/dist/esm/internal/Walk.js.map +1 -0
  477. package/docs/README.md +117 -0
  478. package/docs/api.md +472 -0
  479. package/docs/concepts/effects-and-capabilities.md +136 -0
  480. package/docs/concepts/flows-and-handlers.md +115 -0
  481. package/docs/concepts/limits-and-disclosure.md +102 -0
  482. package/docs/concepts/search-contract.md +182 -0
  483. package/docs/guides/bind-the-standard-flows.md +114 -0
  484. package/docs/guides/implement-a-search-peer.md +133 -0
  485. package/docs/guides/pin-a-checkpoint.md +141 -0
  486. package/docs/guides/query-a-language-server.md +257 -0
  487. package/docs/guides/reach-the-network.md +141 -0
  488. package/docs/guides/read-and-edit-a-file.md +240 -0
  489. package/docs/guides/run-a-shell-command.md +215 -0
  490. package/docs/guides/run-the-test-suite.md +154 -0
  491. package/docs/guides/search-a-workspace.md +148 -0
  492. package/docs/installation.md +111 -0
  493. package/docs/quickstart.md +125 -0
  494. package/docs/reference/flows.md +556 -0
  495. package/docs/troubleshooting.md +322 -0
  496. package/package.json +439 -3
  497. package/src/ApplyPatch.ts +458 -0
  498. package/src/Bash.ts +607 -0
  499. package/src/Checkpoints.ts +422 -0
  500. package/src/Container.ts +212 -0
  501. package/src/Edit.ts +366 -0
  502. package/src/ExaWebSearch.ts +145 -0
  503. package/src/Explore.ts +139 -0
  504. package/src/Fetch.ts +117 -0
  505. package/src/Glob.ts +200 -0
  506. package/src/Grep.ts +320 -0
  507. package/src/HttpPost.ts +120 -0
  508. package/src/LanguageServer.ts +110 -0
  509. package/src/Ls.ts +228 -0
  510. package/src/Lsp.ts +184 -0
  511. package/src/Manifest.ts +162 -0
  512. package/src/NativeSearch.ts +457 -0
  513. package/src/NodeLanguageServer.ts +1148 -0
  514. package/src/PortableSearch.ts +264 -0
  515. package/src/Probe.ts +284 -0
  516. package/src/Read.ts +359 -0
  517. package/src/Relocate.ts +166 -0
  518. package/src/Search.ts +185 -0
  519. package/src/SearchConformance.ts +288 -0
  520. package/src/SearchContract.ts +489 -0
  521. package/src/ShellCommand.ts +201 -0
  522. package/src/StdError.ts +80 -0
  523. package/src/TestRun.ts +401 -0
  524. package/src/TestRunner.ts +119 -0
  525. package/src/TreeFingerprint.ts +200 -0
  526. package/src/UpdatePlan.ts +171 -0
  527. package/src/WebFetch.ts +194 -0
  528. package/src/WebSearch.ts +165 -0
  529. package/src/Write.ts +195 -0
  530. package/src/index.ts +154 -0
  531. package/src/internal/ApplyPatch.ts +601 -0
  532. package/src/internal/CodexText.ts +182 -0
  533. package/src/internal/Declaration.ts +60 -0
  534. package/src/internal/Diagnostics.ts +134 -0
  535. package/src/internal/EnvelopePrecheck.ts +233 -0
  536. package/src/internal/Exec.ts +314 -0
  537. package/src/internal/FileMutation.ts +231 -0
  538. package/src/internal/FsFailure.ts +51 -0
  539. package/src/internal/GitWorktree.ts +491 -0
  540. package/src/internal/Grouping.ts +106 -0
  541. package/src/internal/Html.ts +87 -0
  542. package/src/internal/Http.ts +209 -0
  543. package/src/internal/HttpNetwork.ts +153 -0
  544. package/src/internal/Ignore.ts +135 -0
  545. package/src/internal/LinearRegex.ts +173 -0
  546. package/src/internal/Match.ts +172 -0
  547. package/src/internal/Paths.ts +42 -0
  548. package/src/internal/Preserve.ts +108 -0
  549. package/src/internal/Prune.ts +62 -0
  550. package/src/internal/SearchContract.ts +77 -0
  551. package/src/internal/Symbols.ts +88 -0
  552. package/src/internal/TestReport.ts +194 -0
  553. package/src/internal/Text.ts +170 -0
  554. package/src/internal/Url.ts +23 -0
  555. package/src/internal/Walk.ts +176 -0
@@ -0,0 +1,1148 @@
1
+ /**
2
+ * Host-backed Language Server Protocol client over the permission-checked
3
+ * process spawner.
4
+ *
5
+ * LSP is framed JSON-RPC over ordinary stdio pipes, so the client spawns
6
+ * through `@smthrs/kernel`'s `ChildProcessSpawner`; a terminal is never
7
+ * involved.
8
+ *
9
+ * @since 1.0.0
10
+ */
11
+
12
+ import * as ChildProcessEnvironment from "@smthrs/kernel/ChildProcessEnvironment"
13
+ import * as ChildProcessSpawner from "@smthrs/kernel/ChildProcessSpawner"
14
+ import { Clock, Deferred, Effect, Fiber, Layer, Option, Queue, type Scope, Semaphore, Stream } from "effect"
15
+ import * as ChildProcess from "effect/unstable/process/ChildProcess"
16
+ import { createHash } from "node:crypto"
17
+ import * as NodeFs from "node:fs"
18
+ import * as NodePath from "node:path"
19
+ import { fileURLToPath, pathToFileURL } from "node:url"
20
+ import * as LanguageServer from "./LanguageServer.ts"
21
+ import * as StdError from "./StdError.ts"
22
+
23
+ /**
24
+ * Maximum frames buffered for a language server's standard input.
25
+ *
26
+ * Bounded offers apply backpressure, and each offer uses the request timeout
27
+ * so a server that stops reading produces a typed timeout instead of an
28
+ * unbounded queue or a new hang.
29
+ *
30
+ * @category constants
31
+ * @since 1.0.0
32
+ */
33
+ export const MAX_QUEUED_FRAMES = 256
34
+
35
+ /**
36
+ * Maximum concurrent JSON-RPC requests awaiting a response.
37
+ *
38
+ * Every entry already has a timeout, and process exit or stdout closure fails
39
+ * all entries. This cap is the last-resort bound for a host that fans out
40
+ * requests without limit.
41
+ *
42
+ * @category constants
43
+ * @since 1.0.0
44
+ */
45
+ export const MAX_PENDING_REQUESTS = 512
46
+
47
+ const DEFAULT_SETTLE_MS = 5_000
48
+ const DEFAULT_QUIET_MS = 300
49
+
50
+ /**
51
+ * A request every server must refuse at once: the specification has a server
52
+ * answer an unknown `$/` request with MethodNotFound.
53
+ */
54
+ const BARRIER_METHOD = "$/smithers/barrier"
55
+ /** A server that has not answered the barrier in this long is not asked again. */
56
+ const BARRIER_TIMEOUT_MS = 1_000
57
+
58
+ /**
59
+ * One host language-server process.
60
+ *
61
+ * Response headers may contain at most 8 KiB, and response bodies may contain
62
+ * at most 8 MiB.
63
+ *
64
+ * @category models
65
+ * @since 1.0.0
66
+ */
67
+ export interface Config {
68
+ /**
69
+ * The file extensions this server answers for, such as `[".ts", ".tsx"]`.
70
+ * A server without `extensions` answers every file no other server claims.
71
+ */
72
+ readonly extensions?: ReadonlyArray<string> | undefined
73
+ readonly command: string
74
+ readonly args?: ReadonlyArray<string> | undefined
75
+ readonly cwd: string
76
+ readonly environment?: Readonly<Record<string, string>> | undefined
77
+ /**
78
+ * Sent as `initializationOptions` on `initialize`. A host pins where the
79
+ * server loads its own code from here, for example
80
+ * `{ tsserver: { path: "<host copy>/tsserver.js" } }` for
81
+ * typescript-language-server, which otherwise runs the workspace's own
82
+ * `node_modules/typescript`.
83
+ */
84
+ readonly initializationOptions?: unknown
85
+ readonly timeoutMs?: number | undefined
86
+ /**
87
+ * How long `diagnostics` waits for a server without pull diagnostics to
88
+ * publish for the latest synced text, then fails with `timeout`. Defaults to
89
+ * 5 seconds. typescript-language-server publishes nothing for a change that
90
+ * leaves a file with no problems, so such a change always waits this long.
91
+ */
92
+ readonly settleMs?: number | undefined
93
+ /**
94
+ * After a publish, how long `diagnostics` waits for a later one before
95
+ * answering, within `settleMs`. A server may publish syntax errors before
96
+ * semantic ones: typescript-language-server does on the first open of a
97
+ * file. Defaults to 300 milliseconds.
98
+ */
99
+ readonly quietMs?: number | undefined
100
+ }
101
+
102
+ interface JsonRpcMessage {
103
+ readonly id?: unknown
104
+ readonly method?: unknown
105
+ readonly params?: unknown
106
+ readonly result?: unknown
107
+ readonly error?: unknown
108
+ }
109
+
110
+ /** JSON-RPC's code for a method the server does not implement. */
111
+ const METHOD_NOT_FOUND = -32601
112
+
113
+ const LANGUAGE_IDS: Readonly<Record<string, string>> = {
114
+ ".ts": "typescript",
115
+ ".mts": "typescript",
116
+ ".cts": "typescript",
117
+ ".tsx": "typescriptreact",
118
+ ".js": "javascript",
119
+ ".mjs": "javascript",
120
+ ".cjs": "javascript",
121
+ ".jsx": "javascriptreact",
122
+ ".py": "python",
123
+ ".rs": "rust",
124
+ ".go": "go",
125
+ ".rb": "ruby",
126
+ ".sh": "shellscript",
127
+ ".md": "markdown",
128
+ ".yml": "yaml",
129
+ ".cc": "cpp",
130
+ ".cpp": "cpp",
131
+ ".hpp": "cpp",
132
+ ".cs": "csharp"
133
+ }
134
+
135
+ const extensionOf = (path: string): string => NodePath.extname(path).toLowerCase()
136
+
137
+ const languageId = (path: string): string => {
138
+ const extension = extensionOf(path)
139
+ return LANGUAGE_IDS[extension] ?? (extension.slice(1) || "plaintext")
140
+ }
141
+
142
+ interface FrameDecoder {
143
+ readonly push: (chunk: Uint8Array) => ReadonlyArray<FrameEvent>
144
+ }
145
+
146
+ type FrameEvent =
147
+ | { readonly _tag: "Message"; readonly value: unknown }
148
+ | { readonly _tag: "Failure"; readonly error: StdError.StdError; readonly id?: number | undefined }
149
+
150
+ const failure = (code: StdError.Code, message: string): StdError.StdError => new StdError.StdError({ code, message })
151
+
152
+ const maximumHeaderBytes = 8 * 1024
153
+ const maximumFrameBytes = 8 * 1024 * 1024
154
+ const headerEnd = new Uint8Array([13, 10, 13, 10])
155
+ const contentLengthPrefix = new TextEncoder().encode("Content-Length:")
156
+
157
+ const concatenate = (left: Uint8Array, right: Uint8Array): Uint8Array => {
158
+ const combined = new Uint8Array(left.length + right.length)
159
+ combined.set(left)
160
+ combined.set(right, left.length)
161
+ return combined
162
+ }
163
+
164
+ interface ChunkBuffer {
165
+ readonly length: number
166
+ readonly push: (chunk: Uint8Array) => void
167
+ readonly indexOf: (needle: Uint8Array) => number
168
+ // Counts come from delimiter offsets or validated Content-Length values.
169
+ // A reader can only be acquired for an available, nonnegative prefix.
170
+ readonly prefix: (length: number) => {
171
+ readonly take: () => Uint8Array
172
+ readonly discard: () => void
173
+ } | undefined
174
+ }
175
+
176
+ const makeChunkBuffer = (): ChunkBuffer => {
177
+ let chunks: Array<Uint8Array> = []
178
+ let head = 0
179
+ let headOffset = 0
180
+ let length = 0
181
+
182
+ const compact = (): void => {
183
+ if (head === chunks.length) {
184
+ chunks = []
185
+ head = 0
186
+ headOffset = 0
187
+ } else if (head >= 64 && head * 2 >= chunks.length) {
188
+ chunks = chunks.slice(head)
189
+ head = 0
190
+ }
191
+ }
192
+
193
+ const consume = (count: number, output?: Uint8Array): void => {
194
+ let remaining = count
195
+ let outputOffset = 0
196
+ for (const chunk of chunks.slice(head)) {
197
+ if (remaining === 0) break
198
+ const available = chunk.byteLength - headOffset
199
+ const consumed = Math.min(available, remaining)
200
+ if (output !== undefined) {
201
+ output.set(chunk.subarray(headOffset, headOffset + consumed), outputOffset)
202
+ outputOffset += consumed
203
+ }
204
+ headOffset += consumed
205
+ length -= consumed
206
+ remaining -= consumed
207
+ if (headOffset === chunk.byteLength) {
208
+ head++
209
+ headOffset = 0
210
+ }
211
+ }
212
+ compact()
213
+ }
214
+
215
+ return {
216
+ get length() {
217
+ return length
218
+ },
219
+ push: (chunk) => {
220
+ if (chunk.byteLength === 0) return
221
+ // Own the bytes so caller mutation or resizing cannot change buffered lengths.
222
+ chunks.push(chunk.slice())
223
+ length += chunk.byteLength
224
+ },
225
+ indexOf: (needle) => {
226
+ let absolute = 0
227
+ let matched = 0
228
+ for (let chunkIndex = head; chunkIndex < chunks.length; chunkIndex++) {
229
+ const chunk = chunks[chunkIndex]!
230
+ const start = chunkIndex === head ? headOffset : 0
231
+ for (let index = start; index < chunk.byteLength; index++) {
232
+ const byte = chunk[index]
233
+ if (byte === needle[matched]) matched++
234
+ else matched = byte === needle[0] ? 1 : 0
235
+ if (matched === needle.byteLength) return absolute - needle.byteLength + 1
236
+ absolute++
237
+ }
238
+ }
239
+ return -1
240
+ },
241
+ prefix: (count) => {
242
+ if (count > length) return undefined
243
+ return {
244
+ take: () => {
245
+ const output = new Uint8Array(count)
246
+ consume(count, output)
247
+ return output
248
+ },
249
+ discard: () => consume(count)
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ const malformedId = (body: string): number | undefined => {
256
+ const match = /"id"\s*:\s*(\d+)/.exec(body)
257
+ if (match === null) return undefined
258
+ const id = Number(match[1])
259
+ return Number.isSafeInteger(id) ? id : undefined
260
+ }
261
+
262
+ const makeFrameDecoder = (): FrameDecoder => {
263
+ const pending = makeChunkBuffer()
264
+ let bodyLength: number | undefined
265
+ let resynchronizing = false
266
+ return {
267
+ push: (chunk) => {
268
+ pending.push(chunk)
269
+ const events: Array<FrameEvent> = []
270
+ while (pending.length > 0 || bodyLength === 0) {
271
+ if (resynchronizing) {
272
+ const nextHeader = pending.indexOf(contentLengthPrefix)
273
+ if (nextHeader < 0) {
274
+ pending.prefix(Math.max(0, pending.length - contentLengthPrefix.byteLength + 1))!.discard()
275
+ break
276
+ }
277
+ pending.prefix(nextHeader)!.discard()
278
+ resynchronizing = false
279
+ }
280
+ if (bodyLength !== undefined) {
281
+ const reader = pending.prefix(bodyLength)
282
+ if (reader === undefined) break
283
+ const body = new TextDecoder().decode(reader.take())
284
+ bodyLength = undefined
285
+ try {
286
+ events.push({ _tag: "Message", value: JSON.parse(body) as unknown })
287
+ } catch {
288
+ events.push({
289
+ _tag: "Failure",
290
+ error: failure("request_failed", "Language server returned malformed JSON"),
291
+ id: malformedId(body)
292
+ })
293
+ }
294
+ continue
295
+ }
296
+ const delimiter = pending.indexOf(headerEnd)
297
+ if (delimiter < 0) {
298
+ if (pending.length > maximumHeaderBytes) {
299
+ events.push({
300
+ _tag: "Failure",
301
+ error: failure(
302
+ "request_failed",
303
+ `Language server frame header exceeded ${maximumHeaderBytes} bytes`
304
+ )
305
+ })
306
+ pending.prefix(1)!.discard()
307
+ resynchronizing = true
308
+ continue
309
+ }
310
+ break
311
+ }
312
+ if (delimiter > maximumHeaderBytes) {
313
+ events.push({
314
+ _tag: "Failure",
315
+ error: failure("request_failed", `Language server frame header exceeded ${maximumHeaderBytes} bytes`)
316
+ })
317
+ pending.prefix(delimiter + headerEnd.byteLength)!.discard()
318
+ resynchronizing = true
319
+ continue
320
+ }
321
+ const header = new TextDecoder().decode(pending.prefix(delimiter)!.take())
322
+ pending.prefix(headerEnd.byteLength)!.discard()
323
+ const match = /(?:^|\r\n)Content-Length:\s*(\d+)(?:\r\n|$)/i.exec(header)
324
+ if (match === null) {
325
+ events.push({
326
+ _tag: "Failure",
327
+ error: failure("request_failed", "Language server frame omitted Content-Length")
328
+ })
329
+ resynchronizing = true
330
+ continue
331
+ }
332
+ const length = Number(match[1])
333
+ if (!Number.isSafeInteger(length) || length < 0 || length > maximumFrameBytes) {
334
+ events.push({
335
+ _tag: "Failure",
336
+ error: failure("request_failed", `Language server frame exceeded ${maximumFrameBytes} bytes`)
337
+ })
338
+ resynchronizing = true
339
+ continue
340
+ }
341
+ bodyLength = length
342
+ }
343
+ return events
344
+ }
345
+ }
346
+ }
347
+
348
+ const asMessage = (value: unknown): JsonRpcMessage | undefined =>
349
+ typeof value === "object" && value !== null && !Array.isArray(value)
350
+ ? value
351
+ : undefined
352
+
353
+ const frame = (message: unknown): Uint8Array => {
354
+ const body = new TextEncoder().encode(JSON.stringify(message))
355
+ const header = new TextEncoder().encode(`Content-Length: ${body.byteLength}\r\n\r\n`)
356
+ return concatenate(header, body)
357
+ }
358
+
359
+ const firstCallHierarchyItem = (value: unknown): unknown | undefined => Array.isArray(value) ? value[0] : undefined
360
+
361
+ const isFile = (path: string): boolean => {
362
+ try {
363
+ return NodeFs.statSync(path, { throwIfNoEntry: false })?.isFile() === true
364
+ } catch {
365
+ return false
366
+ }
367
+ }
368
+
369
+ /** Whether anything, a file or a directory, exists at `path`. */
370
+ const exists = (path: string): boolean => {
371
+ try {
372
+ return NodeFs.statSync(path, { throwIfNoEntry: false }) !== undefined
373
+ } catch {
374
+ return false
375
+ }
376
+ }
377
+
378
+ const realPath = (path: string): string => {
379
+ try {
380
+ return NodeFs.realpathSync(path)
381
+ } catch {
382
+ return NodePath.resolve(path)
383
+ }
384
+ }
385
+
386
+ const isWithin = (root: string, path: string): boolean => {
387
+ const relative = NodePath.relative(root, path)
388
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${NodePath.sep}`) &&
389
+ !NodePath.isAbsolute(relative))
390
+ }
391
+
392
+ /**
393
+ * Resolves the program a spawn would execute: a command containing a path
394
+ * separator against `cwd`, a bare name through the child's `PATH`, where an
395
+ * empty or relative entry is relative to `cwd`.
396
+ */
397
+ const resolveCommand = (command: string, cwd: string, path: string | undefined): string | undefined => {
398
+ if (command.includes("/") || command.includes(NodePath.sep)) {
399
+ const candidate = NodePath.resolve(cwd, command)
400
+ return isFile(candidate) ? candidate : undefined
401
+ }
402
+ for (const entry of (path ?? "").split(NodePath.delimiter)) {
403
+ const candidate = NodePath.resolve(cwd, entry, command)
404
+ if (!isFile(candidate)) continue
405
+ try {
406
+ NodeFs.accessSync(candidate, NodeFs.constants.X_OK)
407
+ return candidate
408
+ } catch {
409
+ continue
410
+ }
411
+ }
412
+ return undefined
413
+ }
414
+
415
+ /**
416
+ * Refuses a server whose program or any file-naming argument lies in the
417
+ * workspace. The server runs on the host as the host user, so a program the
418
+ * workspace supplies (its `node_modules/.bin`, a `./` script, a symlink it
419
+ * planted) would let workspace files choose what the host executes.
420
+ */
421
+ /**
422
+ * Programs whose job is to run another program named in their arguments or
423
+ * found in the working directory: shells, `env`, and package runners. Their
424
+ * arguments are code or package names, not files, so the workspace check below
425
+ * cannot see what they would execute (`sh -c node_modules/.bin/x`, `npx x`).
426
+ */
427
+ const LAUNCHERS = new Set([
428
+ "sh",
429
+ "bash",
430
+ "zsh",
431
+ "dash",
432
+ "ksh",
433
+ "fish",
434
+ "csh",
435
+ "tcsh",
436
+ "env",
437
+ "npx",
438
+ "pnpx",
439
+ "npm",
440
+ "pnpm",
441
+ "yarn",
442
+ "bunx",
443
+ "deno",
444
+ "cmd",
445
+ "powershell",
446
+ "pwsh"
447
+ ])
448
+
449
+ /** Interpreter flags that take code or a module to load instead of a file path. */
450
+ const CODE_FLAGS = new Set(["-e", "--eval", "-p", "--print", "-r", "--require", "--import", "--loader"])
451
+
452
+ const launcher = (config: Config): string | undefined => {
453
+ const name = NodePath.basename(config.command).toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/, "")
454
+ if (LAUNCHERS.has(name)) return name
455
+ if (name !== "node" && name !== "bun" && name !== "nodejs") return undefined
456
+ const args = config.args ?? []
457
+ if (name === "bun" && ["x", "run", "exec"].includes(args[0] ?? "")) return `bun ${args[0]}`
458
+ const flag = args.find((argument) => CODE_FLAGS.has(argument.split("=")[0]!) || argument === "--experimental-loader")
459
+ return flag === undefined ? undefined : `${name} ${flag.split("=")[0]}`
460
+ }
461
+
462
+ const workspaceProgram = (
463
+ config: Config,
464
+ path: string | undefined
465
+ ): StdError.StdError | undefined => {
466
+ const launching = launcher(config)
467
+ if (launching !== undefined) {
468
+ return failure(
469
+ "permission_denied",
470
+ `Language server command runs ${launching}, which executes a program its arguments or the workspace choose; name the server's own host binary instead`
471
+ )
472
+ }
473
+ const lexicalRoot = NodePath.resolve(config.cwd)
474
+ const realRoot = realPath(lexicalRoot)
475
+ const inWorkspace = (file: string): boolean =>
476
+ isWithin(lexicalRoot, file) || isWithin(realRoot, file) || isWithin(realRoot, realPath(file)) ||
477
+ isWithin(lexicalRoot, realPath(file))
478
+ const program = resolveCommand(config.command, lexicalRoot, path)
479
+ if (program !== undefined && inWorkspace(program)) {
480
+ return failure(
481
+ "permission_denied",
482
+ `Language server program ${program} is inside the workspace ${lexicalRoot}; run a host copy instead`
483
+ )
484
+ }
485
+ for (const argument of config.args ?? []) {
486
+ for (const value of [argument, argument.slice(argument.indexOf("=") + 1)]) {
487
+ if (value === "") continue
488
+ const candidate = NodePath.resolve(lexicalRoot, value)
489
+ // A directory counts: `node <dir>` runs `<dir>/index.js` or its package main.
490
+ if (exists(candidate) && inWorkspace(candidate)) {
491
+ return failure(
492
+ "permission_denied",
493
+ `Language server argument names ${candidate}, a path inside the workspace ${lexicalRoot}`
494
+ )
495
+ }
496
+ }
497
+ }
498
+ return undefined
499
+ }
500
+
501
+ interface PublishedDiagnostics {
502
+ readonly version: number | undefined
503
+ readonly sequence: number
504
+ readonly diagnostics: ReadonlyArray<unknown>
505
+ }
506
+
507
+ interface OpenDocument {
508
+ readonly version: number
509
+ /** The publish sequence number when this version was sent. */
510
+ readonly sentAt: number
511
+ /** The SHA-256 of the text last sent, so `refresh` resends only changed files. */
512
+ readonly digest: string
513
+ }
514
+
515
+ const digestOf = (text: string): string => createHash("sha256").update(text).digest("hex")
516
+
517
+ const makeClient = (
518
+ config: Config
519
+ ): Effect.Effect<
520
+ LanguageServer.LanguageServer,
521
+ StdError.StdError,
522
+ ChildProcessSpawner.ChildProcessSpawner | Scope.Scope
523
+ > =>
524
+ Effect.gen(function*() {
525
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
526
+ const timeoutMs = config.timeoutMs ?? 30_000
527
+ const settleMs = config.settleMs ?? DEFAULT_SETTLE_MS
528
+ const quietMs = config.quietMs ?? DEFAULT_QUIET_MS
529
+ const env = ChildProcessEnvironment.make(process.env, config.environment)
530
+ const refused = yield* Effect.sync(() => workspaceProgram(config, env.PATH))
531
+ if (refused !== undefined) return yield* Effect.fail(refused)
532
+ // The process's stdin is fed from this queue for the client's lifetime, so
533
+ // each request is one offered frame and the pipe never closes between them.
534
+ const input = yield* Queue.bounded<Uint8Array>(MAX_QUEUED_FRAMES)
535
+ const handle = yield* spawner.spawn(
536
+ ChildProcess.make(config.command, config.args ?? [], {
537
+ cwd: config.cwd,
538
+ env,
539
+ extendEnv: false,
540
+ stdin: { stream: Stream.fromQueue(input), endOnDone: false }
541
+ })
542
+ ).pipe(
543
+ Effect.mapError((error) =>
544
+ failure("provider_unavailable", `Language server process could not be started: ${error.message}`)
545
+ )
546
+ )
547
+ const pending = new Map<number, Deferred.Deferred<unknown, StdError.StdError>>()
548
+ const decoder = makeFrameDecoder()
549
+ let nextId = 1
550
+ let terminalError: StdError.StdError | undefined
551
+
552
+ // Keep draining stderr while requests run. Copy retained bytes so a small
553
+ // tail never holds a larger backing buffer alive.
554
+ const maximumStderrBytes = 64 * 1024
555
+ let stderrTail: Uint8Array = new Uint8Array(0)
556
+ const stderrFiber = yield* handle.stderr.pipe(
557
+ Stream.runForEach((chunk) =>
558
+ Effect.sync(() => {
559
+ stderrTail = chunk.byteLength >= maximumStderrBytes
560
+ ? chunk.slice(-maximumStderrBytes)
561
+ : concatenate(
562
+ stderrTail.slice(Math.max(0, stderrTail.byteLength + chunk.byteLength - maximumStderrBytes)),
563
+ chunk
564
+ )
565
+ })
566
+ ),
567
+ Effect.catch((cause) => Effect.logWarning("Language server stderr stream failed", cause)),
568
+ Effect.forkScoped({ startImmediately: true })
569
+ )
570
+ const stderr = (): string => {
571
+ let start = 0
572
+ while (start < stderrTail.byteLength && start < 3 && (stderrTail[start]! & 0b1100_0000) === 0b1000_0000) start++
573
+ return new TextDecoder().decode(stderrTail.subarray(start))
574
+ }
575
+
576
+ const failPending = (error: StdError.StdError): Effect.Effect<void> =>
577
+ Effect.flatMap(
578
+ Effect.sync(() => {
579
+ const deferreds = [...pending.values()]
580
+ pending.clear()
581
+ return deferreds
582
+ }),
583
+ (deferreds) => Effect.forEach(deferreds, (deferred) => Deferred.fail(deferred, error), { discard: true })
584
+ )
585
+
586
+ const failRequest = (id: number, error: StdError.StdError): Effect.Effect<void> => {
587
+ const deferred = pending.get(id)
588
+ if (deferred === undefined) return Effect.void
589
+ pending.delete(id)
590
+ return Deferred.fail(deferred, error).pipe(Effect.asVoid)
591
+ }
592
+
593
+ const closeWith = (error: StdError.StdError): Effect.Effect<void> =>
594
+ Effect.gen(function*() {
595
+ // stdout EOF and process exit can arrive before the last stderr chunk.
596
+ // A bounded grace also handles servers that close stdout but stay alive.
597
+ yield* Fiber.await(stderrFiber).pipe(Effect.timeoutOption(100))
598
+ terminalError ??= error
599
+ yield* failPending(terminalError)
600
+ })
601
+
602
+ const uriOf = (path: string): string => pathToFileURL(NodePath.resolve(config.cwd, path)).href
603
+ const documents = new Map<string, OpenDocument>()
604
+ // Versions keep climbing across a close and reopen, so a late publish for
605
+ // the closed text never passes for the reopened one.
606
+ const versions = new Map<string, number>()
607
+ const published = new Map<string, PublishedDiagnostics>()
608
+ const listeners = new Map<string, Set<Deferred.Deferred<void>>>()
609
+ let publishSequence = 0
610
+ let pullUnsupported = false
611
+ let pullSupported = false
612
+ let versionedPublishes = false
613
+ let barrierAnswered = true
614
+
615
+ const publish = (params: unknown): Effect.Effect<void> => {
616
+ const notification = asMessage(params) as {
617
+ readonly uri?: unknown
618
+ readonly version?: unknown
619
+ readonly diagnostics?: unknown
620
+ } | undefined
621
+ if (typeof notification?.uri !== "string" || !Array.isArray(notification.diagnostics)) return Effect.void
622
+ const uri = notification.uri
623
+ const version = typeof notification.version === "number" ? notification.version : undefined
624
+ if (version !== undefined) versionedPublishes = true
625
+ const previous = published.get(uri)?.version
626
+ // A late publish for an older version never replaces a newer one.
627
+ if (version !== undefined && previous !== undefined && version < previous) return Effect.void
628
+ published.set(uri, {
629
+ version,
630
+ sequence: ++publishSequence,
631
+ diagnostics: notification.diagnostics
632
+ })
633
+ const waiting = listeners.get(uri)
634
+ if (waiting === undefined) return Effect.void
635
+ listeners.delete(uri)
636
+ return Effect.forEach(waiting, (listener) => Deferred.succeed(listener, undefined), { discard: true })
637
+ }
638
+
639
+ const receive = (value: unknown): Effect.Effect<void> => {
640
+ const message = asMessage(value)
641
+ if (message?.method === "textDocument/publishDiagnostics") return publish(message.params)
642
+ if (message === undefined || typeof message.id !== "number") return Effect.void
643
+ const deferred = pending.get(message.id)
644
+ if (deferred === undefined) return Effect.void
645
+ pending.delete(message.id)
646
+ if (message.error === undefined) return Deferred.succeed(deferred, message.result)
647
+ const error = asMessage(message.error) as {
648
+ readonly code?: unknown
649
+ readonly message?: unknown
650
+ readonly data?: unknown
651
+ } | undefined
652
+ return Deferred.fail(
653
+ deferred,
654
+ new StdError.StdError({
655
+ code: "request_failed",
656
+ message: typeof error?.message === "string" ? error.message : "Language server returned a JSON-RPC error",
657
+ ...(typeof error?.code === "number" && typeof error.message === "string"
658
+ ? {
659
+ rpcError: {
660
+ code: error.code,
661
+ message: error.message,
662
+ ...(error.data === undefined ? {} : { data: error.data })
663
+ }
664
+ }
665
+ : {})
666
+ })
667
+ )
668
+ }
669
+
670
+ const receiveFrame = (event: FrameEvent): Effect.Effect<void> =>
671
+ event._tag === "Message"
672
+ ? receive(event.value)
673
+ // A rejected frame has no trustworthy response id. Failing every
674
+ // waiter prevents another request from hanging behind corrupt framing.
675
+ : event.id === undefined
676
+ ? failPending(event.error)
677
+ : failRequest(event.id, event.error)
678
+
679
+ yield* handle.stdout.pipe(
680
+ Stream.runForEach((chunk) => Effect.forEach(decoder.push(chunk), receiveFrame, { discard: true })),
681
+ Effect.matchEffect({
682
+ onFailure: () => closeWith(failure("request_failed", "Language server output stream failed")),
683
+ onSuccess: () => closeWith(failure("request_failed", "Language server output stream closed"))
684
+ }),
685
+ Effect.forkScoped({ startImmediately: true })
686
+ )
687
+
688
+ yield* handle.exitCode.pipe(
689
+ Effect.flatMap((exitCode) =>
690
+ closeWith(failure("request_failed", `Language server process exited with code ${exitCode}`))
691
+ ),
692
+ Effect.catch(() => closeWith(failure("request_failed", "Language server process exited"))),
693
+ Effect.forkScoped({ startImmediately: true })
694
+ )
695
+
696
+ const send = (message: unknown): Effect.Effect<void, StdError.StdError> =>
697
+ Effect.suspend(() =>
698
+ terminalError === undefined
699
+ ? Queue.offer(input, frame(message)).pipe(
700
+ Effect.asVoid,
701
+ Effect.timeout(timeoutMs),
702
+ Effect.mapError(() =>
703
+ failure(
704
+ "timeout",
705
+ `Language server stdin is not being drained; frame offer exceeded ${timeoutMs}ms`
706
+ )
707
+ )
708
+ )
709
+ : Effect.fail(terminalError)
710
+ )
711
+
712
+ const request = (
713
+ method: string,
714
+ params: unknown
715
+ ): Effect.Effect<unknown, StdError.StdError> =>
716
+ Effect.gen(function*() {
717
+ const deferred = yield* Deferred.make<unknown, StdError.StdError>()
718
+ const registered = yield* Effect.sync((): number | StdError.StdError => {
719
+ if (terminalError !== undefined) return terminalError
720
+ if (pending.size >= MAX_PENDING_REQUESTS) {
721
+ return failure(
722
+ "request_failed",
723
+ `Language server reached the ${MAX_PENDING_REQUESTS} pending-request cap`
724
+ )
725
+ }
726
+ const id = nextId++
727
+ pending.set(id, deferred)
728
+ return id
729
+ })
730
+ if (registered instanceof StdError.StdError) return yield* Effect.fail(registered)
731
+ const id = registered
732
+ return yield* Effect.gen(function*() {
733
+ yield* send({ jsonrpc: "2.0", id, method, params })
734
+ return yield* Deferred.await(deferred).pipe(
735
+ Effect.timeout(timeoutMs),
736
+ Effect.mapError((cause) =>
737
+ cause instanceof StdError.StdError
738
+ ? cause
739
+ : failure("timeout", `Language server request timed out: ${method}`)
740
+ )
741
+ )
742
+ }).pipe(
743
+ Effect.ensuring(
744
+ Effect.sync(() => {
745
+ pending.delete(id)
746
+ })
747
+ )
748
+ )
749
+ }).pipe(Effect.mapError((error) =>
750
+ new StdError.StdError({
751
+ ...error,
752
+ method,
753
+ message: error.rpcError === undefined ? error.message : `${method}: ${error.message}`,
754
+ ...(stderrTail.byteLength === 0 ? {} : { stderr: stderr() })
755
+ })
756
+ ))
757
+
758
+ const notify = (method: string, params: unknown): Effect.Effect<void, StdError.StdError> =>
759
+ send({ jsonrpc: "2.0", method, params })
760
+
761
+ yield* request("initialize", {
762
+ processId: null,
763
+ rootUri: pathToFileURL(config.cwd).href,
764
+ capabilities: {
765
+ textDocument: {
766
+ synchronization: { dynamicRegistration: false },
767
+ publishDiagnostics: { versionSupport: true },
768
+ diagnostic: { dynamicRegistration: false }
769
+ }
770
+ },
771
+ ...(config.initializationOptions === undefined ? {} : { initializationOptions: config.initializationOptions })
772
+ })
773
+ yield* notify("initialized", {})
774
+
775
+ const positionParams = (position: LanguageServer.Position) => ({
776
+ textDocument: { uri: uriOf(position.path) },
777
+ position: { line: position.line, character: position.character }
778
+ })
779
+
780
+ // A publish without a version is read by arrival: it counts for the text
781
+ // sent before it. Before each sync, a request the server answers at once
782
+ // drains what it wrote earlier, such as the empty report
783
+ // typescript-language-server publishes when a file closes, so that report
784
+ // never passes for the reopened text. Unneeded once the server has pulled
785
+ // or versioned a publish, and dropped if the server does not answer it.
786
+ const barrier: Effect.Effect<void> = Effect.suspend(() =>
787
+ pullSupported || versionedPublishes || !barrierAnswered
788
+ ? Effect.void
789
+ : request(BARRIER_METHOD, null).pipe(
790
+ Effect.timeoutOption(Math.min(BARRIER_TIMEOUT_MS, timeoutMs)),
791
+ Effect.flatMap((answer) =>
792
+ Effect.sync(() => {
793
+ if (Option.isNone(answer)) barrierAnswered = false
794
+ })
795
+ ),
796
+ // The answer is MethodNotFound; any answer drains what came before it.
797
+ Effect.ignore
798
+ )
799
+ )
800
+
801
+ // One sync at a time, so versions reach the server in the order they are numbered.
802
+ const syncLock = yield* Semaphore.make(1)
803
+ const sync = (path: string, text: string): Effect.Effect<void, StdError.StdError> =>
804
+ syncLock.withPermit(Effect.andThen(barrier, () => {
805
+ const uri = uriOf(path)
806
+ const open = documents.get(uri)
807
+ const version = (versions.get(uri) ?? 0) + 1
808
+ const sentAt = publishSequence
809
+ const message = open === undefined
810
+ ? notify("textDocument/didOpen", {
811
+ textDocument: { uri, languageId: languageId(path), version, text }
812
+ })
813
+ : notify("textDocument/didChange", { textDocument: { uri, version }, contentChanges: [{ text }] })
814
+ return message.pipe(
815
+ Effect.tap(() =>
816
+ Effect.sync(() => {
817
+ versions.set(uri, version)
818
+ documents.set(uri, { version, sentAt, digest: digestOf(text) })
819
+ })
820
+ )
821
+ )
822
+ }))
823
+
824
+ const closeUri = (uri: string): Effect.Effect<void, StdError.StdError> =>
825
+ Effect.suspend(() => {
826
+ if (!documents.has(uri)) return Effect.void
827
+ documents.delete(uri)
828
+ published.delete(uri)
829
+ return notify("textDocument/didClose", { textDocument: { uri } })
830
+ })
831
+
832
+ const close = (path: string): Effect.Effect<void, StdError.StdError> => syncLock.withPermit(closeUri(uriOf(path)))
833
+
834
+ // Re-reads every open document: a file changed on disk is resent, and a
835
+ // file that is gone is closed. What changed it (a shell command, say) is
836
+ // not something the server can see.
837
+ const refresh: Effect.Effect<void, StdError.StdError> = Effect.suspend(() =>
838
+ Effect.forEach([...documents], ([uri, open]) => {
839
+ const path = fileURLToPath(uri)
840
+ let text: string | undefined
841
+ try {
842
+ text = NodeFs.readFileSync(path, "utf8")
843
+ } catch {
844
+ text = undefined
845
+ }
846
+ if (text === undefined) return syncLock.withPermit(closeUri(uri))
847
+ return digestOf(text) === open.digest ? Effect.void : sync(path, text)
848
+ }, { discard: true })
849
+ )
850
+
851
+ /** What the server published for the document's latest synced text, if it has. */
852
+ const current = (uri: string): PublishedDiagnostics | undefined => {
853
+ const entry = published.get(uri)
854
+ const open = documents.get(uri)
855
+ if (entry === undefined || open === undefined) return undefined
856
+ const fresh = entry.version === undefined ? entry.sequence > open.sentAt : entry.version >= open.version
857
+ return fresh ? entry : undefined
858
+ }
859
+
860
+ /** The first publish for the latest synced text whose sequence is past `after`. */
861
+ const awaitPublished = (uri: string, after = 0): Effect.Effect<PublishedDiagnostics> =>
862
+ Effect.suspend(() => {
863
+ const entry = current(uri)
864
+ if (entry !== undefined && entry.sequence > after) return Effect.succeed(entry)
865
+ return Effect.flatMap(Deferred.make<void>(), (listener) => {
866
+ const waiting = listeners.get(uri) ?? new Set()
867
+ waiting.add(listener)
868
+ listeners.set(uri, waiting)
869
+ return Deferred.await(listener).pipe(
870
+ Effect.ensuring(Effect.sync(() => {
871
+ listeners.get(uri)?.delete(listener)
872
+ })),
873
+ Effect.flatMap(() => awaitPublished(uri, after))
874
+ )
875
+ })
876
+ })
877
+
878
+ const pushedDiagnostics = (path: string): Effect.Effect<unknown, StdError.StdError> =>
879
+ Effect.gen(function*() {
880
+ const uri = uriOf(path)
881
+ if (!documents.has(uri)) {
882
+ // A push-only server publishes only for open documents.
883
+ const text = yield* Effect.try({
884
+ try: () => NodeFs.readFileSync(NodePath.resolve(config.cwd, path), "utf8"),
885
+ catch: () => new StdError.StdError({ code: "not_found", message: `File not found: ${path}`, path })
886
+ })
887
+ yield* sync(path, text)
888
+ }
889
+ const started = yield* Clock.currentTimeMillis
890
+ const entry = yield* awaitPublished(uri).pipe(Effect.timeoutOption(settleMs))
891
+ if (Option.isNone(entry)) {
892
+ return yield* Effect.fail(
893
+ new StdError.StdError({
894
+ code: "timeout",
895
+ message: `Language server published no diagnostics for ${path} within ${settleMs}ms`,
896
+ method: "textDocument/publishDiagnostics",
897
+ path
898
+ })
899
+ )
900
+ }
901
+ // A later publish within the quiet window supersedes this one.
902
+ let latest = entry.value
903
+ for (;;) {
904
+ const remaining = settleMs - ((yield* Clock.currentTimeMillis) - started)
905
+ if (remaining <= 0) break
906
+ const next = yield* awaitPublished(uri, latest.sequence).pipe(
907
+ Effect.timeoutOption(Math.min(quietMs, remaining))
908
+ )
909
+ if (Option.isNone(next)) break
910
+ latest = next.value
911
+ }
912
+ return { kind: "full", items: latest.diagnostics }
913
+ })
914
+
915
+ const diagnostics = (path: string): Effect.Effect<unknown, StdError.StdError> =>
916
+ pullUnsupported
917
+ ? pushedDiagnostics(path)
918
+ : request("textDocument/diagnostic", { textDocument: { uri: uriOf(path) } }).pipe(
919
+ Effect.tap(() =>
920
+ Effect.sync(() => {
921
+ pullSupported = true
922
+ })
923
+ ),
924
+ Effect.catchIf(
925
+ (error) => error.rpcError?.code === METHOD_NOT_FOUND,
926
+ () => {
927
+ pullUnsupported = true
928
+ return pushedDiagnostics(path)
929
+ }
930
+ )
931
+ )
932
+
933
+ const prepareCallHierarchy = (position: LanguageServer.Position) =>
934
+ request("textDocument/prepareCallHierarchy", positionParams(position))
935
+ const callHierarchy = (
936
+ method: "callHierarchy/incomingCalls" | "callHierarchy/outgoingCalls",
937
+ position: LanguageServer.Position
938
+ ): Effect.Effect<unknown, StdError.StdError> =>
939
+ prepareCallHierarchy(position).pipe(
940
+ Effect.flatMap((prepared) => {
941
+ const item = firstCallHierarchyItem(prepared)
942
+ return item === undefined ? Effect.succeed([]) : request(method, { item })
943
+ })
944
+ )
945
+
946
+ return LanguageServer.make({
947
+ hover: (position) => request("textDocument/hover", positionParams(position)),
948
+ definition: (position) => request("textDocument/definition", positionParams(position)),
949
+ references: (position) =>
950
+ request("textDocument/references", {
951
+ ...positionParams(position),
952
+ context: { includeDeclaration: true }
953
+ }),
954
+ implementation: (position) => request("textDocument/implementation", positionParams(position)),
955
+ documentSymbols: (path) => request("textDocument/documentSymbol", { textDocument: { uri: uriOf(path) } }),
956
+ workspaceSymbols: (query) => request("workspace/symbol", { query }),
957
+ prepareCallHierarchy,
958
+ callHierarchyIncoming: (position) => callHierarchy("callHierarchy/incomingCalls", position),
959
+ callHierarchyOutgoing: (position) => callHierarchy("callHierarchy/outgoingCalls", position),
960
+ diagnostics,
961
+ sync,
962
+ close,
963
+ refresh
964
+ })
965
+ })
966
+
967
+ const normalizeExtension = (extension: string): string =>
968
+ (extension.startsWith(".") ? extension : `.${extension}`).toLowerCase()
969
+
970
+ /**
971
+ * Routes each request to the client for its file's extension. `client`
972
+ * starts, or returns, the client at an index; `started` is that client only
973
+ * if it already runs, so `refresh` never starts a server.
974
+ */
975
+ const routed = (
976
+ configs: ReadonlyArray<Config>,
977
+ client: (index: number) => Effect.Effect<LanguageServer.LanguageServer, StdError.StdError>,
978
+ started: (index: number) => LanguageServer.LanguageServer | undefined
979
+ ): LanguageServer.LanguageServer => {
980
+ const claims = configs.map((entry) => entry.extensions?.map(normalizeExtension))
981
+ const fallback = claims.findIndex((claimed) => claimed === undefined)
982
+ const indexOf = (path: string): Effect.Effect<number, StdError.StdError> => {
983
+ const extension = extensionOf(path)
984
+ const claimed = claims.findIndex((entry) => entry?.includes(extension) === true)
985
+ const index = claimed >= 0 ? claimed : fallback
986
+ return index >= 0
987
+ ? Effect.succeed(index)
988
+ : Effect.fail(
989
+ new StdError.StdError({
990
+ code: "unsupported",
991
+ message: `No language server is configured for ${extension || "extensionless"} files`,
992
+ path
993
+ })
994
+ )
995
+ }
996
+ const route = (path: string): Effect.Effect<LanguageServer.LanguageServer, StdError.StdError> =>
997
+ Effect.flatMap(indexOf(path), client)
998
+ const byPosition = (
999
+ method:
1000
+ | "hover"
1001
+ | "definition"
1002
+ | "references"
1003
+ | "implementation"
1004
+ | "prepareCallHierarchy"
1005
+ | "callHierarchyIncoming"
1006
+ | "callHierarchyOutgoing"
1007
+ ) =>
1008
+ (position: LanguageServer.Position) => Effect.flatMap(route(position.path), (server) => server[method](position))
1009
+ const indices = configs.map((_, index) => index)
1010
+ return LanguageServer.make({
1011
+ hover: byPosition("hover"),
1012
+ definition: byPosition("definition"),
1013
+ references: byPosition("references"),
1014
+ implementation: byPosition("implementation"),
1015
+ prepareCallHierarchy: byPosition("prepareCallHierarchy"),
1016
+ callHierarchyIncoming: byPosition("callHierarchyIncoming"),
1017
+ callHierarchyOutgoing: byPosition("callHierarchyOutgoing"),
1018
+ documentSymbols: (path) => Effect.flatMap(route(path), (server) => server.documentSymbols(path)),
1019
+ diagnostics: (path) => Effect.flatMap(route(path), (server) => server.diagnostics(path)),
1020
+ sync: (path, text) => Effect.flatMap(route(path), (server) => server.sync(path, text)),
1021
+ // A server that never started holds no open file.
1022
+ close: (path) => Effect.flatMap(indexOf(path), (index) => started(index)?.close(path) ?? Effect.void),
1023
+ refresh: Effect.suspend(() =>
1024
+ Effect.forEach(indices, (index) => started(index)?.refresh ?? Effect.void, { discard: true })
1025
+ ),
1026
+ workspaceSymbols: (query) =>
1027
+ configs.length === 1 ?
1028
+ Effect.flatMap(client(0), (server) => server.workspaceSymbols(query)) :
1029
+ Effect.forEach(
1030
+ indices,
1031
+ (index) => Effect.flatMap(client(index), (server) => server.workspaceSymbols(query)),
1032
+ { concurrency: "unbounded" }
1033
+ ).pipe(
1034
+ Effect.map((answers) =>
1035
+ answers.flatMap((answer) => Array.isArray(answer) ? answer : answer == null ? [] : [answer])
1036
+ )
1037
+ )
1038
+ })
1039
+ }
1040
+
1041
+ const configList = (
1042
+ config: Config | ReadonlyArray<Config>
1043
+ ): Effect.Effect<ReadonlyArray<Config>, StdError.StdError> => {
1044
+ const configs: ReadonlyArray<Config> = Array.isArray(config) ? config : [config as Config]
1045
+ return configs.length === 0
1046
+ ? Effect.fail(failure("invalid_input", "At least one language server must be configured"))
1047
+ : Effect.succeed(configs)
1048
+ }
1049
+
1050
+ /**
1051
+ * Constructs scoped host language-server clients, one process per config, and
1052
+ * routes each file to the server whose `extensions` include its extension, or
1053
+ * else to the server that declares none. `workspaceSymbols` asks every server
1054
+ * and concatenates their answers.
1055
+ *
1056
+ * Every client opens a file on its first `sync` and sends the full text on
1057
+ * each later one. `diagnostics` pulls `textDocument/diagnostic`; a server that
1058
+ * answers `MethodNotFound` is read from its `textDocument/publishDiagnostics`
1059
+ * notifications instead, waiting up to `settleMs` for the latest synced text.
1060
+ *
1061
+ * Fails with `permission_denied`, before spawning, when the resolved program or
1062
+ * a file or directory named by an argument lies under `config.cwd`, or when the command is a
1063
+ * launcher (a shell, `env`, a package runner such as `npx` or `pnpm`, or `node`
1064
+ * and `bun` given inline code or a preload) whose arguments choose what runs.
1065
+ *
1066
+ * @category constructors
1067
+ * @since 1.0.0
1068
+ */
1069
+ export const make = (
1070
+ config: Config | ReadonlyArray<Config>
1071
+ ): Effect.Effect<
1072
+ LanguageServer.LanguageServer,
1073
+ StdError.StdError,
1074
+ ChildProcessSpawner.ChildProcessSpawner | Scope.Scope
1075
+ > =>
1076
+ Effect.gen(function*() {
1077
+ const configs = yield* configList(config)
1078
+ const clients = yield* Effect.forEach(configs, makeClient)
1079
+ return routed(configs, (index) => Effect.succeed(clients[index]!), (index) => clients[index])
1080
+ })
1081
+
1082
+ /**
1083
+ * {@link make}, starting each server when the first request for one of its
1084
+ * files arrives rather than up front, so a host can bind servers it may never
1085
+ * need. A server starts with the services and authority of the fiber that
1086
+ * built this service, not those of the request that first needs it. A server
1087
+ * that fails to start fails that request and every later one for its files.
1088
+ * `refresh` and `close` never start a server.
1089
+ *
1090
+ * The workspace-program refusals of {@link make} still apply up front.
1091
+ *
1092
+ * @category constructors
1093
+ * @since 1.0.0
1094
+ */
1095
+ export const makeLazy = (
1096
+ config: Config | ReadonlyArray<Config>
1097
+ ): Effect.Effect<
1098
+ LanguageServer.LanguageServer,
1099
+ StdError.StdError,
1100
+ ChildProcessSpawner.ChildProcessSpawner | Scope.Scope
1101
+ > =>
1102
+ Effect.gen(function*() {
1103
+ const configs = yield* configList(config)
1104
+ for (const entry of configs) {
1105
+ const refused = workspaceProgram(entry, ChildProcessEnvironment.make(process.env, entry.environment).PATH)
1106
+ if (refused !== undefined) return yield* Effect.fail(refused)
1107
+ }
1108
+ const clients: Array<LanguageServer.LanguageServer | undefined> = configs.map(() => undefined)
1109
+ // Each server starts in a fiber forked here, so it starts with the
1110
+ // services and authority of whoever built this service, never those of
1111
+ // the request that first needs it, and lives as long as this scope.
1112
+ const starts = yield* Effect.forEach(configs, (entry, index) =>
1113
+ Effect.gen(function*() {
1114
+ const wanted = yield* Deferred.make<void>()
1115
+ const ready = yield* Deferred.make<LanguageServer.LanguageServer, StdError.StdError>()
1116
+ yield* Deferred.await(wanted).pipe(
1117
+ Effect.andThen(makeClient(entry)),
1118
+ Effect.tap((client) => Effect.sync(() => void (clients[index] = client))),
1119
+ Effect.exit,
1120
+ Effect.flatMap((exit) => Deferred.done(ready, exit)),
1121
+ Effect.forkScoped({ startImmediately: true })
1122
+ )
1123
+ return Deferred.succeed(wanted, undefined).pipe(Effect.andThen(Deferred.await(ready)))
1124
+ }))
1125
+ return routed(configs, (index) => starts[index]!, (index) => clients[index])
1126
+ })
1127
+
1128
+ /**
1129
+ * Provides a scoped host language-server implementation.
1130
+ *
1131
+ * @category layers
1132
+ * @since 1.0.0
1133
+ */
1134
+ export const layer = (
1135
+ config: Config | ReadonlyArray<Config>
1136
+ ): Layer.Layer<LanguageServer.LanguageServer, StdError.StdError, ChildProcessSpawner.ChildProcessSpawner> =>
1137
+ Layer.effect(LanguageServer.LanguageServer, make(config))
1138
+
1139
+ /**
1140
+ * Provides {@link makeLazy}.
1141
+ *
1142
+ * @category layers
1143
+ * @since 1.0.0
1144
+ */
1145
+ export const layerLazy = (
1146
+ config: Config | ReadonlyArray<Config>
1147
+ ): Layer.Layer<LanguageServer.LanguageServer, StdError.StdError, ChildProcessSpawner.ChildProcessSpawner> =>
1148
+ Layer.effect(LanguageServer.LanguageServer, makeLazy(config))