@linxiraos/pi-utils 1.0.0

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 (244) hide show
  1. package/CHANGELOG.md +449 -0
  2. package/README.md +36 -0
  3. package/dist/types/abortable.d.ts +32 -0
  4. package/dist/types/acp/connection.d.ts +118 -0
  5. package/dist/types/acp/protocol.d.ts +526 -0
  6. package/dist/types/acp/schema.d.ts +41 -0
  7. package/dist/types/acp/stream.d.ts +8 -0
  8. package/dist/types/acp/transport.d.ts +81 -0
  9. package/dist/types/acp.d.ts +6 -0
  10. package/dist/types/async.d.ts +20 -0
  11. package/dist/types/binary.d.ts +17 -0
  12. package/dist/types/browsers.d.ts +68 -0
  13. package/dist/types/chalk.d.ts +125 -0
  14. package/dist/types/cli.d.ts +134 -0
  15. package/dist/types/color.d.ts +102 -0
  16. package/dist/types/dates.d.ts +7 -0
  17. package/dist/types/dirs.d.ts +282 -0
  18. package/dist/types/docx/converter.d.ts +46 -0
  19. package/dist/types/docx/xml.d.ts +26 -0
  20. package/dist/types/docx/zip.d.ts +6 -0
  21. package/dist/types/docx.d.ts +11 -0
  22. package/dist/types/dom/core.d.ts +431 -0
  23. package/dist/types/dom/parser.d.ts +7 -0
  24. package/dist/types/dom/selector.d.ts +5 -0
  25. package/dist/types/dom.d.ts +5 -0
  26. package/dist/types/env.d.ts +124 -0
  27. package/dist/types/fetch-retry.d.ts +96 -0
  28. package/dist/types/file-lock.d.ts +23 -0
  29. package/dist/types/format.d.ts +37 -0
  30. package/dist/types/frontmatter.d.ts +46 -0
  31. package/dist/types/fs-error.d.ts +31 -0
  32. package/dist/types/glob.d.ts +28 -0
  33. package/dist/types/headers.d.ts +34 -0
  34. package/dist/types/index.d.ts +39 -0
  35. package/dist/types/json-parse.d.ts +74 -0
  36. package/dist/types/json.d.ts +14 -0
  37. package/dist/types/logger/rotating-file.d.ts +18 -0
  38. package/dist/types/logger.d.ts +96 -0
  39. package/dist/types/loop-phase.d.ts +10 -0
  40. package/dist/types/lru.d.ts +46 -0
  41. package/dist/types/marked/core.d.ts +445 -0
  42. package/dist/types/marked.d.ts +2 -0
  43. package/dist/types/mermaid-ascii.d.ts +11 -0
  44. package/dist/types/mime.d.ts +29 -0
  45. package/dist/types/module-timer.d.ts +1 -0
  46. package/dist/types/path-tree.d.ts +76 -0
  47. package/dist/types/path.d.ts +2 -0
  48. package/dist/types/peek-file.d.ts +29 -0
  49. package/dist/types/postmortem.d.ts +104 -0
  50. package/dist/types/process-name.d.ts +7 -0
  51. package/dist/types/procmgr.d.ts +74 -0
  52. package/dist/types/prompt.d.ts +18 -0
  53. package/dist/types/ptree.d.ts +110 -0
  54. package/dist/types/readability/readability.d.ts +9 -0
  55. package/dist/types/readability/readerable.d.ts +10 -0
  56. package/dist/types/readability/types.d.ts +70 -0
  57. package/dist/types/readability.d.ts +4 -0
  58. package/dist/types/ring.d.ts +93 -0
  59. package/dist/types/runtime-install.d.ts +78 -0
  60. package/dist/types/sanitize-text.d.ts +29 -0
  61. package/dist/types/snowflake.d.ts +25 -0
  62. package/dist/types/stderr-guard.d.ts +22 -0
  63. package/dist/types/stream.d.ts +67 -0
  64. package/dist/types/tab-spacing.d.ts +24 -0
  65. package/dist/types/temp.d.ts +17 -0
  66. package/dist/types/template.d.ts +62 -0
  67. package/dist/types/timing-buffer.d.ts +22 -0
  68. package/dist/types/tls-fetch.d.ts +37 -0
  69. package/dist/types/turndown/gfm.d.ts +11 -0
  70. package/dist/types/turndown/html.d.ts +5 -0
  71. package/dist/types/turndown/service.d.ts +21 -0
  72. package/dist/types/turndown/types.d.ts +70 -0
  73. package/dist/types/turndown.d.ts +4 -0
  74. package/dist/types/type-guards.d.ts +5 -0
  75. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
  76. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
  77. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
  78. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
  79. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
  80. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
  81. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
  82. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
  83. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
  84. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
  85. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
  86. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
  87. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
  88. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
  89. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
  90. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
  91. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
  92. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
  93. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
  94. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
  95. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
  96. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
  97. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
  98. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
  99. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
  100. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
  101. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
  102. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
  103. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
  104. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
  105. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
  106. package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
  107. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
  108. package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
  109. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
  110. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
  111. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
  112. package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
  113. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
  114. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
  115. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
  116. package/dist/types/version.d.ts +18 -0
  117. package/dist/types/vterm/buffer.d.ts +99 -0
  118. package/dist/types/vterm/terminal.d.ts +44 -0
  119. package/dist/types/vterm.d.ts +8 -0
  120. package/dist/types/which.d.ts +37 -0
  121. package/dist/types/worker-host.d.ts +51 -0
  122. package/dist/types/xml.d.ts +31 -0
  123. package/package.json +59 -0
  124. package/src/abortable.ts +109 -0
  125. package/src/acp/connection.ts +344 -0
  126. package/src/acp/protocol.ts +466 -0
  127. package/src/acp/schema.ts +160 -0
  128. package/src/acp/stream.ts +82 -0
  129. package/src/acp/transport.ts +213 -0
  130. package/src/acp.ts +6 -0
  131. package/src/async.ts +91 -0
  132. package/src/binary.ts +50 -0
  133. package/src/browsers.ts +501 -0
  134. package/src/chalk.ts +312 -0
  135. package/src/cli.ts +529 -0
  136. package/src/color.ts +302 -0
  137. package/src/dates.ts +194 -0
  138. package/src/dirs.ts +1026 -0
  139. package/src/docx/converter.ts +681 -0
  140. package/src/docx/xml.ts +166 -0
  141. package/src/docx/zip.ts +87 -0
  142. package/src/docx.ts +20 -0
  143. package/src/dom/core.ts +1254 -0
  144. package/src/dom/parser.ts +370 -0
  145. package/src/dom/selector.ts +290 -0
  146. package/src/dom.ts +33 -0
  147. package/src/env.ts +400 -0
  148. package/src/fetch-retry.ts +379 -0
  149. package/src/file-lock.ts +69 -0
  150. package/src/format.ts +113 -0
  151. package/src/frontmatter.ts +205 -0
  152. package/src/fs-error.ts +56 -0
  153. package/src/glob.ts +189 -0
  154. package/src/headers.ts +167 -0
  155. package/src/index.ts +59 -0
  156. package/src/json-parse.ts +821 -0
  157. package/src/json.ts +23 -0
  158. package/src/logger/rotating-file.ts +149 -0
  159. package/src/logger.ts +798 -0
  160. package/src/loop-phase.ts +49 -0
  161. package/src/lru.ts +185 -0
  162. package/src/marked/core.ts +1576 -0
  163. package/src/marked.ts +2 -0
  164. package/src/mermaid-ascii.ts +31 -0
  165. package/src/mime.ts +159 -0
  166. package/src/module-timer.ts +148 -0
  167. package/src/path-tree.ts +147 -0
  168. package/src/path.ts +28 -0
  169. package/src/peek-file.ts +188 -0
  170. package/src/postmortem.ts +433 -0
  171. package/src/process-name.ts +57 -0
  172. package/src/procmgr.ts +247 -0
  173. package/src/prompt.ts +545 -0
  174. package/src/ptree.ts +412 -0
  175. package/src/readability/readability.ts +533 -0
  176. package/src/readability/readerable.ts +51 -0
  177. package/src/readability/types.ts +72 -0
  178. package/src/readability.ts +11 -0
  179. package/src/ring.ts +169 -0
  180. package/src/runtime-install.ts +393 -0
  181. package/src/sanitize-text.ts +96 -0
  182. package/src/snowflake.ts +121 -0
  183. package/src/stderr-guard.ts +155 -0
  184. package/src/stream.ts +454 -0
  185. package/src/tab-spacing.ts +336 -0
  186. package/src/temp.ts +134 -0
  187. package/src/template.ts +586 -0
  188. package/src/timing-buffer.ts +47 -0
  189. package/src/tls-fetch.ts +178 -0
  190. package/src/turndown/gfm.ts +106 -0
  191. package/src/turndown/html.ts +257 -0
  192. package/src/turndown/service.ts +334 -0
  193. package/src/turndown/types.ts +81 -0
  194. package/src/turndown.ts +5 -0
  195. package/src/type-guards.ts +17 -0
  196. package/src/vendor/mermaid-ascii/NOTICE +33 -0
  197. package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
  198. package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
  199. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
  200. package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
  201. package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
  202. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
  203. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
  204. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
  205. package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
  206. package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
  207. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
  208. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
  209. package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
  210. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
  211. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
  212. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
  213. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
  214. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
  215. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
  216. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
  217. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
  218. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
  219. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
  220. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
  221. package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
  222. package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
  223. package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
  224. package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
  225. package/src/vendor/mermaid-ascii/class/types.ts +121 -0
  226. package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
  227. package/src/vendor/mermaid-ascii/er/types.ts +91 -0
  228. package/src/vendor/mermaid-ascii/index.ts +14 -0
  229. package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
  230. package/src/vendor/mermaid-ascii/parser.ts +645 -0
  231. package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
  232. package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
  233. package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
  234. package/src/vendor/mermaid-ascii/types.ts +164 -0
  235. package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
  236. package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
  237. package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
  238. package/src/version.ts +99 -0
  239. package/src/vterm/buffer.ts +218 -0
  240. package/src/vterm/terminal.ts +773 -0
  241. package/src/vterm.ts +8 -0
  242. package/src/which.ts +234 -0
  243. package/src/worker-host.ts +100 -0
  244. package/src/xml.ts +313 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Default tab width (display / tab expansion) and per-file width from `.editorconfig`.
3
+ * Mirrors former `pi-natives` `indent` + `text` default-tab-width behavior (no N-API).
4
+ */
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ import { isFsError } from "./fs-error";
8
+
9
+ export const MIN_TAB_WIDTH = 1;
10
+ export const MAX_TAB_WIDTH = 16;
11
+ export const DEFAULT_TAB_WIDTH = 3;
12
+
13
+ /**
14
+ * Per-component path length cap on common filesystems (`NAME_MAX = 255` on
15
+ * Linux ext4 / macOS APFS / Windows NTFS). Paths with components longer than
16
+ * this cannot be opened at all, so editorconfig discovery short-circuits to
17
+ * the default instead of running into `ENAMETOOLONG` from `readFileSync`.
18
+ */
19
+ const NAME_MAX_BYTES = 255;
20
+
21
+ const EDITORCONFIG_NAME = ".editorconfig";
22
+
23
+ const editorConfigCache = new Map<string, ParsedEditorConfig | null>();
24
+ const editorConfigChainCache = new Map<string, ChainEntry[]>();
25
+
26
+ interface EditorConfigSection {
27
+ pattern: string;
28
+ properties: Map<string, string>;
29
+ }
30
+
31
+ interface ParsedEditorConfig {
32
+ root: boolean;
33
+ sections: EditorConfigSection[];
34
+ }
35
+
36
+ interface ChainEntry {
37
+ dir: string;
38
+ parsed: ParsedEditorConfig;
39
+ }
40
+
41
+ const enum IndentStyle {
42
+ Space,
43
+ Tab,
44
+ }
45
+
46
+ type IndentSize = { kind: "spaces"; n: number } | { kind: "tab" };
47
+
48
+ interface EditorConfigMatch {
49
+ indentStyle?: IndentStyle;
50
+ indentSize?: IndentSize;
51
+ tabWidth?: number;
52
+ }
53
+
54
+ function clampTabWidth(value: number): number {
55
+ return Math.min(MAX_TAB_WIDTH, Math.max(MIN_TAB_WIDTH, Math.trunc(value)));
56
+ }
57
+
58
+ function parsePositiveInteger(raw: string | undefined): number | undefined {
59
+ if (raw === undefined) return undefined;
60
+ if (!/^\d+$/.test(raw)) return undefined;
61
+ const parsed = Number.parseInt(raw, 10);
62
+ if (parsed === 0) return undefined;
63
+ return clampTabWidth(parsed);
64
+ }
65
+
66
+ function fixUnclosedBraces(pattern: string): string {
67
+ const opens = [...pattern].filter(c => c === "{").length;
68
+ const closes = [...pattern].filter(c => c === "}").length;
69
+ if (opens > closes) {
70
+ return pattern + "}".repeat(opens - closes);
71
+ }
72
+ return pattern;
73
+ }
74
+
75
+ /** Match `crates/pi-natives/src/glob_util.rs` `build_glob_pattern`. */
76
+ function buildGlobPattern(globStr: string, recursive: boolean): string {
77
+ const normalized = globStr.replace(/\\/g, "/");
78
+ const pattern =
79
+ !recursive || normalized.includes("/") || normalized.startsWith("**") ? normalized : `**/${normalized}`;
80
+ return fixUnclosedBraces(pattern);
81
+ }
82
+
83
+ function globMatches(pattern: string, relativePath: string): boolean {
84
+ try {
85
+ const g = new Bun.Glob(pattern);
86
+ return g.match(relativePath);
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ function matchesEditorConfigPattern(pattern: string, relativePath: string): boolean {
93
+ const normalized = pattern.replace(/^\/+/, "");
94
+ if (!normalized) {
95
+ return false;
96
+ }
97
+
98
+ const candidates = normalized.includes("/")
99
+ ? [buildGlobPattern(normalized, false)]
100
+ : [buildGlobPattern(normalized, false), buildGlobPattern(normalized, true)];
101
+
102
+ for (const p of candidates) {
103
+ if (globMatches(p, relativePath)) {
104
+ return true;
105
+ }
106
+ }
107
+ return false;
108
+ }
109
+
110
+ function parseEditorConfigFile(content: string): ParsedEditorConfig {
111
+ const parsed: ParsedEditorConfig = { root: false, sections: [] };
112
+ let currentSectionIdx: number | undefined;
113
+
114
+ for (const rawLine of content.split(/\n/)) {
115
+ const line = rawLine.trim();
116
+ if (line === "") continue;
117
+ if (line.startsWith("#") || line.startsWith(";")) continue;
118
+
119
+ if (line.startsWith("[") && line.endsWith("]") && line.length >= 2) {
120
+ const secPattern = line.slice(1, -1).trim();
121
+ if (secPattern === "") {
122
+ currentSectionIdx = undefined;
123
+ continue;
124
+ }
125
+ parsed.sections.push({ pattern: secPattern, properties: new Map() });
126
+ currentSectionIdx = parsed.sections.length - 1;
127
+ continue;
128
+ }
129
+
130
+ const eq = line.indexOf("=");
131
+ if (eq === -1) continue;
132
+ const key = line.slice(0, eq).trim().toLowerCase();
133
+ const value = line
134
+ .slice(eq + 1)
135
+ .trim()
136
+ .toLowerCase();
137
+ if (key === "") continue;
138
+
139
+ if (currentSectionIdx !== undefined) {
140
+ parsed.sections[currentSectionIdx]!.properties.set(key, value);
141
+ } else if (key === "root") {
142
+ parsed.root = value === "true";
143
+ }
144
+ }
145
+
146
+ return parsed;
147
+ }
148
+
149
+ function parseCachedEditorConfig(configPath: string): ParsedEditorConfig | undefined {
150
+ const key = path.resolve(configPath);
151
+ const hit = editorConfigCache.get(key);
152
+ if (hit !== undefined) {
153
+ return hit ?? undefined;
154
+ }
155
+
156
+ let content: string;
157
+ try {
158
+ content = fs.readFileSync(key, "utf8");
159
+ } catch (err) {
160
+ // editorconfig discovery is best-effort. Any filesystem error
161
+ // (`ENOENT`, `ENAMETOOLONG`, `ENOTDIR`, `EACCES`, `ELOOP`, `EINVAL`,
162
+ // …) means "no usable config at this path" — never a fatal condition
163
+ // for callers like the edit renderer that hand us arbitrary strings.
164
+ if (isFsError(err)) return undefined;
165
+ throw err;
166
+ }
167
+ const parsed = parseEditorConfigFile(content);
168
+ editorConfigCache.set(key, parsed);
169
+ return parsed;
170
+ }
171
+
172
+ function resolveFilePath(projectDir: string, file: string): string {
173
+ if (path.isAbsolute(file)) {
174
+ return path.normalize(path.resolve(file));
175
+ }
176
+ return path.normalize(path.resolve(projectDir, file));
177
+ }
178
+
179
+ /** Like `pathdiff::diff_paths` + forward slashes (see `indent.rs`). */
180
+ function relativePathUnified(baseDir: string, absoluteFile: string): string {
181
+ const base = path.resolve(baseDir);
182
+ const file = path.resolve(absoluteFile);
183
+ const rel = path.relative(base, file);
184
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
185
+ return ".";
186
+ }
187
+ return rel.replace(/\\/g, "/");
188
+ }
189
+
190
+ function collectEditorConfigChain(startDir: string): ChainEntry[] {
191
+ const key = path.resolve(startDir);
192
+ const cached = editorConfigChainCache.get(key);
193
+ if (cached !== undefined) {
194
+ return cached;
195
+ }
196
+
197
+ const chain: ChainEntry[] = [];
198
+ let cursor = key;
199
+ for (;;) {
200
+ const configPath = path.join(cursor, EDITORCONFIG_NAME);
201
+ const parsed = parseCachedEditorConfig(configPath);
202
+ if (parsed !== undefined) {
203
+ chain.push({ dir: cursor, parsed });
204
+ if (parsed.root) {
205
+ break;
206
+ }
207
+ }
208
+
209
+ const parent = path.dirname(cursor);
210
+ if (parent === cursor) {
211
+ break;
212
+ }
213
+ cursor = parent;
214
+ }
215
+
216
+ chain.reverse();
217
+ editorConfigChainCache.set(key, chain);
218
+ return chain;
219
+ }
220
+
221
+ function resolveEditorConfigMatch(absoluteFile: string): EditorConfigMatch | undefined {
222
+ const fileDir = path.dirname(absoluteFile);
223
+ const chain = collectEditorConfigChain(fileDir);
224
+ if (chain.length === 0) {
225
+ return undefined;
226
+ }
227
+
228
+ const match: EditorConfigMatch = {};
229
+ for (const { dir, parsed } of chain) {
230
+ const relativePath = relativePathUnified(dir, absoluteFile);
231
+ for (const section of parsed.sections) {
232
+ if (!matchesEditorConfigPattern(section.pattern, relativePath)) {
233
+ continue;
234
+ }
235
+
236
+ const style = section.properties.get("indent_style");
237
+ if (style === "space") {
238
+ match.indentStyle = IndentStyle.Space;
239
+ } else if (style === "tab") {
240
+ match.indentStyle = IndentStyle.Tab;
241
+ }
242
+
243
+ const rawSize = section.properties.get("indent_size");
244
+ if (rawSize === "tab") {
245
+ match.indentSize = { kind: "tab" };
246
+ } else if (rawSize !== undefined) {
247
+ const n = parsePositiveInteger(rawSize);
248
+ if (n !== undefined) {
249
+ match.indentSize = { kind: "spaces", n };
250
+ }
251
+ }
252
+
253
+ const tw = parsePositiveInteger(section.properties.get("tab_width"));
254
+ if (tw !== undefined) {
255
+ match.tabWidth = tw;
256
+ }
257
+ }
258
+ }
259
+
260
+ if (match.indentStyle === undefined && match.indentSize === undefined && match.tabWidth === undefined) {
261
+ return undefined;
262
+ }
263
+ return match;
264
+ }
265
+
266
+ function hasOverlongPathComponent(filePath: string): boolean {
267
+ for (const part of filePath.split(/[\\/]/)) {
268
+ if (part.length > 0 && Buffer.byteLength(part) > NAME_MAX_BYTES) {
269
+ return true;
270
+ }
271
+ }
272
+ return false;
273
+ }
274
+
275
+ /**
276
+ * `.editorconfig`-derived formatting options for an LSP `textDocument/formatting` request.
277
+ *
278
+ * Both fields are absent when the resolved `.editorconfig` chain does not pin them, so callers
279
+ * can layer their own fallbacks (content sniffing, project defaults) underneath. Returned values
280
+ * are clamped to {@link MIN_TAB_WIDTH}..{@link MAX_TAB_WIDTH}.
281
+ */
282
+ export interface EditorConfigFormatting {
283
+ /** Effective indent width in columns, from `indent_size` or `tab_width`. */
284
+ tabSize?: number;
285
+ /** `true` for `indent_style = space`, `false` for `indent_style = tab` (or `indent_size = tab`). */
286
+ insertSpaces?: boolean;
287
+ }
288
+
289
+ /**
290
+ * Resolve `.editorconfig` formatting hints for `file` without falling back to any default.
291
+ *
292
+ * Used by the LSP format-on-write path so a missing `.editorconfig` declaration falls through
293
+ * to caller-provided defaults instead of clobbering the file with the renderer's
294
+ * display tab width (issue #2329).
295
+ */
296
+ export function getEditorConfigFormatting(file?: string | null, projectDir?: string | null): EditorConfigFormatting {
297
+ if (file === undefined || file === null || file === "") {
298
+ return {};
299
+ }
300
+
301
+ const cwd = projectDir ?? process.cwd();
302
+ const absoluteFile = resolveFilePath(cwd, file);
303
+
304
+ // NAME_MAX guard: editorconfig discovery is
305
+ // best-effort and must never escape as `ENAMETOOLONG` from a renderer's
306
+ // stray gibberish path.
307
+ if (hasOverlongPathComponent(absoluteFile)) {
308
+ return {};
309
+ }
310
+
311
+ const match = resolveEditorConfigMatch(absoluteFile);
312
+ if (match === undefined) {
313
+ return {};
314
+ }
315
+
316
+ const result: EditorConfigFormatting = {};
317
+
318
+ if (match.indentSize?.kind === "spaces") {
319
+ result.tabSize = clampTabWidth(match.indentSize.n);
320
+ } else if (match.tabWidth !== undefined) {
321
+ result.tabSize = clampTabWidth(match.tabWidth);
322
+ }
323
+
324
+ if (match.indentStyle === IndentStyle.Space) {
325
+ result.insertSpaces = true;
326
+ } else if (match.indentStyle === IndentStyle.Tab || match.indentSize?.kind === "tab") {
327
+ result.insertSpaces = false;
328
+ } else if (match.indentSize?.kind === "spaces") {
329
+ // `indent_size = <n>` without an explicit `indent_style` is universally
330
+ // read as "indent with N spaces" — both VSCode and Sublime infer
331
+ // `indent_style = space` in that case.
332
+ result.insertSpaces = true;
333
+ }
334
+
335
+ return result;
336
+ }
package/src/temp.ts ADDED
@@ -0,0 +1,134 @@
1
+ import * as fs from "node:fs";
2
+ import * as fsPromises from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+
6
+ export class TempDir {
7
+ #path: string;
8
+ private constructor(path: string) {
9
+ this.#path = path;
10
+ }
11
+
12
+ static createSync(prefix?: string): TempDir {
13
+ return new TempDir(fs.mkdtempSync(normalizePrefix(prefix)));
14
+ }
15
+
16
+ static async create(prefix?: string): Promise<TempDir> {
17
+ return new TempDir(await fsPromises.mkdtemp(normalizePrefix(prefix)));
18
+ }
19
+
20
+ #removePromise: Promise<void> | null = null;
21
+
22
+ path(): string {
23
+ return this.#path;
24
+ }
25
+
26
+ absolute(): string {
27
+ return path.resolve(this.#path);
28
+ }
29
+
30
+ remove(): Promise<void> {
31
+ if (this.#removePromise) {
32
+ return this.#removePromise;
33
+ }
34
+ const removePromise = removeWithRetries(this.#path);
35
+ this.#removePromise = removePromise;
36
+ return removePromise;
37
+ }
38
+
39
+ removeSync(): void {
40
+ removeSyncWithRetries(this.#path);
41
+ this.#removePromise = Promise.resolve();
42
+ }
43
+
44
+ toString(): string {
45
+ return this.#path;
46
+ }
47
+
48
+ join(...paths: string[]): string {
49
+ return path.join(this.#path, ...paths);
50
+ }
51
+
52
+ async [Symbol.asyncDispose](): Promise<void> {
53
+ try {
54
+ await this.remove();
55
+ } catch {
56
+ // Ignore cleanup errors
57
+ }
58
+ }
59
+
60
+ [Symbol.dispose](): void {
61
+ try {
62
+ this.removeSync();
63
+ } catch {
64
+ // Ignore cleanup errors
65
+ }
66
+ }
67
+ }
68
+
69
+ const kTempDir = os.tmpdir();
70
+
71
+ function normalizePrefix(prefix?: string): string {
72
+ if (!prefix) {
73
+ return `${kTempDir}${path.sep}pi-temp-`;
74
+ } else if (prefix.startsWith("@")) {
75
+ return path.join(kTempDir, prefix.slice(1));
76
+ }
77
+ return prefix;
78
+ }
79
+
80
+ const kRemoveOptions = { recursive: true, force: true } as const;
81
+ const kRemoveRetries = 40;
82
+ // 50ms × 40 retries = 2s total retry window. Windows holds file locks on
83
+ // SQLite DBs for up to ~1.5s after close(); the previous 25ms (1s total)
84
+ // was too short for some test cleanup scenarios.
85
+ const kRemoveRetryDelayMs = 50;
86
+ const kRetryableRemoveErrorCodes = new Set(["EBUSY", "EPERM", "ENOTEMPTY"]);
87
+ const kSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
88
+
89
+ /** Removes a path recursively, retrying transient Windows deletion failures. */
90
+ export async function removeWithRetries(target: string): Promise<void> {
91
+ for (let attempt = 0; ; attempt++) {
92
+ try {
93
+ await fsPromises.rm(target, kRemoveOptions);
94
+ return;
95
+ } catch (err) {
96
+ if (!shouldRetryRemove(err, attempt)) throw err;
97
+ await Bun.sleep(kRemoveRetryDelayMs);
98
+ }
99
+ }
100
+ }
101
+
102
+ export function removeSyncWithRetries(target: string): void {
103
+ for (let attempt = 0; ; attempt++) {
104
+ try {
105
+ fs.rmSync(target, kRemoveOptions);
106
+ return;
107
+ } catch (err) {
108
+ if (!shouldRetryRemove(err, attempt)) throw err;
109
+ sleepSync(kRemoveRetryDelayMs);
110
+ }
111
+ }
112
+ }
113
+
114
+ function shouldRetryRemove(err: unknown, attempt: number): boolean {
115
+ return attempt < kRemoveRetries && process.platform === "win32" && isRetryableRemoveError(err);
116
+ }
117
+
118
+ function isRetryableRemoveError(err: unknown): boolean {
119
+ return (
120
+ typeof err === "object" &&
121
+ err !== null &&
122
+ "code" in err &&
123
+ typeof err.code === "string" &&
124
+ kRetryableRemoveErrorCodes.has(err.code)
125
+ );
126
+ }
127
+
128
+ function sleepSync(ms: number): void {
129
+ if ("sleepSync" in Bun && typeof Bun.sleepSync === "function") {
130
+ Bun.sleepSync(ms);
131
+ return;
132
+ }
133
+ Atomics.wait(kSleepBuffer, 0, 0, ms);
134
+ }