@nibapp/ui 0.0.0-stage → 0.1.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 (292) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +124 -2
  4. package/TRADEMARKS.md +22 -0
  5. package/dist/_bundled/money/index.d.ts +98 -0
  6. package/dist/_bundled/money/index.js +152 -0
  7. package/dist/_bundled/tokens/index.d.ts +2 -0
  8. package/dist/_bundled/tokens/index.js +2 -0
  9. package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
  10. package/dist/_bundled/tokens/palette.gen.js +206 -0
  11. package/dist/_bundled/tokens/spec.d.ts +291 -0
  12. package/dist/_bundled/tokens/spec.js +221 -0
  13. package/dist/components/accordion.d.ts +27 -0
  14. package/dist/components/accordion.js +66 -0
  15. package/dist/components/alert-dialog.d.ts +59 -0
  16. package/dist/components/alert-dialog.js +82 -0
  17. package/dist/components/alert.d.ts +141 -0
  18. package/dist/components/alert.js +55 -0
  19. package/dist/components/appear.d.ts +21 -0
  20. package/dist/components/appear.js +42 -0
  21. package/dist/components/avatar.d.ts +196 -0
  22. package/dist/components/avatar.js +135 -0
  23. package/dist/components/badge.d.ts +183 -0
  24. package/dist/components/badge.js +68 -0
  25. package/dist/components/block-stack.d.ts +33 -0
  26. package/dist/components/block-stack.js +197 -0
  27. package/dist/components/button.d.ts +327 -0
  28. package/dist/components/button.js +101 -0
  29. package/dist/components/card.d.ts +131 -0
  30. package/dist/components/card.js +100 -0
  31. package/dist/components/checkbox.d.ts +34 -0
  32. package/dist/components/checkbox.js +37 -0
  33. package/dist/components/choice-row.d.ts +104 -0
  34. package/dist/components/choice-row.js +56 -0
  35. package/dist/components/clamped-text.d.ts +17 -0
  36. package/dist/components/clamped-text.js +47 -0
  37. package/dist/components/code-block.d.ts +48 -0
  38. package/dist/components/code-block.js +124 -0
  39. package/dist/components/code-input.d.ts +49 -0
  40. package/dist/components/code-input.js +59 -0
  41. package/dist/components/combobox.d.ts +194 -0
  42. package/dist/components/combobox.js +176 -0
  43. package/dist/components/command-palette.d.ts +57 -0
  44. package/dist/components/command-palette.js +83 -0
  45. package/dist/components/context-menu.d.ts +24 -0
  46. package/dist/components/context-menu.js +30 -0
  47. package/dist/components/copy-button.d.ts +9 -0
  48. package/dist/components/copy-button.js +20 -0
  49. package/dist/components/copy-command.d.ts +18 -0
  50. package/dist/components/copy-command.js +19 -0
  51. package/dist/components/data-table.d.ts +240 -0
  52. package/dist/components/data-table.js +63 -0
  53. package/dist/components/dialog.d.ts +80 -0
  54. package/dist/components/dialog.js +85 -0
  55. package/dist/components/diff-lines.d.ts +20 -0
  56. package/dist/components/diff-lines.js +10 -0
  57. package/dist/components/diff-view.d.ts +128 -0
  58. package/dist/components/diff-view.js +675 -0
  59. package/dist/components/disclosure.d.ts +33 -0
  60. package/dist/components/disclosure.js +51 -0
  61. package/dist/components/drawn-check.d.ts +8 -0
  62. package/dist/components/drawn-check.js +16 -0
  63. package/dist/components/edge-resizer.d.ts +26 -0
  64. package/dist/components/edge-resizer.js +53 -0
  65. package/dist/components/empty-state.d.ts +112 -0
  66. package/dist/components/empty-state.js +56 -0
  67. package/dist/components/expand-toggle.d.ts +19 -0
  68. package/dist/components/expand-toggle.js +10 -0
  69. package/dist/components/field.d.ts +48 -0
  70. package/dist/components/field.js +60 -0
  71. package/dist/components/filter-chip.d.ts +47 -0
  72. package/dist/components/filter-chip.js +33 -0
  73. package/dist/components/group.d.ts +10 -0
  74. package/dist/components/group.js +9 -0
  75. package/dist/components/icon-text.d.ts +11 -0
  76. package/dist/components/icon-text.js +10 -0
  77. package/dist/components/image-sources.d.ts +57 -0
  78. package/dist/components/image-sources.js +58 -0
  79. package/dist/components/image-thumbnail.d.ts +34 -0
  80. package/dist/components/image-thumbnail.js +58 -0
  81. package/dist/components/image-viewer.d.ts +60 -0
  82. package/dist/components/image-viewer.js +411 -0
  83. package/dist/components/inline-edit.d.ts +22 -0
  84. package/dist/components/inline-edit.js +51 -0
  85. package/dist/components/input.d.ts +155 -0
  86. package/dist/components/input.js +109 -0
  87. package/dist/components/item-label.d.ts +11 -0
  88. package/dist/components/item-label.js +5 -0
  89. package/dist/components/item-row.d.ts +23 -0
  90. package/dist/components/item-row.js +19 -0
  91. package/dist/components/kbd.d.ts +55 -0
  92. package/dist/components/kbd.js +86 -0
  93. package/dist/components/link-policy.d.ts +24 -0
  94. package/dist/components/link-policy.js +32 -0
  95. package/dist/components/linked-text.d.ts +15 -0
  96. package/dist/components/linked-text.js +48 -0
  97. package/dist/components/loading-region.d.ts +18 -0
  98. package/dist/components/loading-region.js +17 -0
  99. package/dist/components/markdown.d.ts +34 -0
  100. package/dist/components/markdown.js +307 -0
  101. package/dist/components/menu.d.ts +132 -0
  102. package/dist/components/menu.js +102 -0
  103. package/dist/components/message-bubble.d.ts +57 -0
  104. package/dist/components/message-bubble.js +28 -0
  105. package/dist/components/meta-line.d.ts +28 -0
  106. package/dist/components/meta-line.js +36 -0
  107. package/dist/components/money-input.d.ts +23 -0
  108. package/dist/components/money-input.js +34 -0
  109. package/dist/components/native-select.d.ts +120 -0
  110. package/dist/components/native-select.js +85 -0
  111. package/dist/components/page-trail.d.ts +30 -0
  112. package/dist/components/page-trail.js +43 -0
  113. package/dist/components/page.d.ts +80 -0
  114. package/dist/components/page.js +60 -0
  115. package/dist/components/path-text.d.ts +8 -0
  116. package/dist/components/path-text.js +11 -0
  117. package/dist/components/percent-input.d.ts +20 -0
  118. package/dist/components/percent-input.js +32 -0
  119. package/dist/components/popover.d.ts +52 -0
  120. package/dist/components/popover.js +36 -0
  121. package/dist/components/progress.d.ts +389 -0
  122. package/dist/components/progress.js +142 -0
  123. package/dist/components/radio.d.ts +65 -0
  124. package/dist/components/radio.js +54 -0
  125. package/dist/components/remote-image-gate.d.ts +34 -0
  126. package/dist/components/remote-image-gate.js +60 -0
  127. package/dist/components/resizable.d.ts +23 -0
  128. package/dist/components/resizable.js +30 -0
  129. package/dist/components/rolling-number.d.ts +13 -0
  130. package/dist/components/rolling-number.js +69 -0
  131. package/dist/components/scroll-area.d.ts +17 -0
  132. package/dist/components/scroll-area.js +25 -0
  133. package/dist/components/section-label.d.ts +10 -0
  134. package/dist/components/section-label.js +21 -0
  135. package/dist/components/section-nav.d.ts +22 -0
  136. package/dist/components/section-nav.js +80 -0
  137. package/dist/components/segmented-control.d.ts +125 -0
  138. package/dist/components/segmented-control.js +101 -0
  139. package/dist/components/select.d.ts +120 -0
  140. package/dist/components/select.js +89 -0
  141. package/dist/components/separator.d.ts +10 -0
  142. package/dist/components/separator.js +12 -0
  143. package/dist/components/sheet.d.ts +101 -0
  144. package/dist/components/sheet.js +129 -0
  145. package/dist/components/shortcut-recorder.d.ts +31 -0
  146. package/dist/components/shortcut-recorder.js +67 -0
  147. package/dist/components/skeleton.d.ts +31 -0
  148. package/dist/components/skeleton.js +33 -0
  149. package/dist/components/slider.d.ts +138 -0
  150. package/dist/components/slider.js +98 -0
  151. package/dist/components/spinner.d.ts +37 -0
  152. package/dist/components/spinner.js +16 -0
  153. package/dist/components/switch.d.ts +59 -0
  154. package/dist/components/switch.js +56 -0
  155. package/dist/components/tab-strip.d.ts +29 -0
  156. package/dist/components/tab-strip.js +42 -0
  157. package/dist/components/tabs.d.ts +13 -0
  158. package/dist/components/tabs.js +68 -0
  159. package/dist/components/text-link.d.ts +54 -0
  160. package/dist/components/text-link.js +38 -0
  161. package/dist/components/textarea.d.ts +75 -0
  162. package/dist/components/textarea.js +90 -0
  163. package/dist/components/toast.d.ts +74 -0
  164. package/dist/components/toast.js +128 -0
  165. package/dist/components/tooltip.d.ts +50 -0
  166. package/dist/components/tooltip.js +46 -0
  167. package/dist/components/truncated-text.d.ts +15 -0
  168. package/dist/components/truncated-text.js +19 -0
  169. package/dist/i18n/defaults.d.ts +7 -0
  170. package/dist/i18n/defaults.js +7 -0
  171. package/dist/i18n/format.d.ts +84 -0
  172. package/dist/i18n/format.js +169 -0
  173. package/dist/i18n/index.d.ts +6 -0
  174. package/dist/i18n/index.js +8 -0
  175. package/dist/i18n/locales/ru-formatters.d.ts +3 -0
  176. package/dist/i18n/locales/ru-formatters.js +22 -0
  177. package/dist/i18n/locales/ru.d.ts +5 -0
  178. package/dist/i18n/locales/ru.js +185 -0
  179. package/dist/i18n/plural.d.ts +20 -0
  180. package/dist/i18n/plural.js +26 -0
  181. package/dist/i18n/provider.d.ts +36 -0
  182. package/dist/i18n/provider.js +39 -0
  183. package/dist/i18n/rich.d.ts +14 -0
  184. package/dist/i18n/rich.js +44 -0
  185. package/dist/i18n/ru-plural.d.ts +3 -0
  186. package/dist/i18n/ru-plural.js +4 -0
  187. package/dist/i18n/strings/alert-dialog.d.ts +5 -0
  188. package/dist/i18n/strings/alert-dialog.js +5 -0
  189. package/dist/i18n/strings/block-stack.d.ts +17 -0
  190. package/dist/i18n/strings/block-stack.js +11 -0
  191. package/dist/i18n/strings/code-block.d.ts +18 -0
  192. package/dist/i18n/strings/code-block.js +13 -0
  193. package/dist/i18n/strings/combobox.d.ts +12 -0
  194. package/dist/i18n/strings/combobox.js +10 -0
  195. package/dist/i18n/strings/command-palette.d.ts +17 -0
  196. package/dist/i18n/strings/command-palette.js +11 -0
  197. package/dist/i18n/strings/diff-view.d.ts +92 -0
  198. package/dist/i18n/strings/diff-view.js +68 -0
  199. package/dist/i18n/strings/image-viewer.d.ts +43 -0
  200. package/dist/i18n/strings/image-viewer.js +32 -0
  201. package/dist/i18n/strings/index.d.ts +314 -0
  202. package/dist/i18n/strings/index.js +43 -0
  203. package/dist/i18n/strings/kbd.d.ts +42 -0
  204. package/dist/i18n/strings/kbd.js +39 -0
  205. package/dist/i18n/strings/markdown.d.ts +23 -0
  206. package/dist/i18n/strings/markdown.js +18 -0
  207. package/dist/i18n/strings/money.d.ts +7 -0
  208. package/dist/i18n/strings/money.js +5 -0
  209. package/dist/i18n/strings/primitives.d.ts +25 -0
  210. package/dist/i18n/strings/primitives.js +18 -0
  211. package/dist/i18n/strings/progress.d.ts +4 -0
  212. package/dist/i18n/strings/progress.js +4 -0
  213. package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
  214. package/dist/i18n/strings/shortcut-recorder.js +11 -0
  215. package/dist/i18n/strings/time.d.ts +41 -0
  216. package/dist/i18n/strings/time.js +20 -0
  217. package/dist/i18n/strings/toast.d.ts +6 -0
  218. package/dist/i18n/strings/toast.js +6 -0
  219. package/dist/i18n/time-zones.d.ts +20 -0
  220. package/dist/i18n/time-zones.js +64 -0
  221. package/dist/index.d.ts +84 -0
  222. package/dist/index.js +88 -0
  223. package/dist/lib/ansi.d.ts +55 -0
  224. package/dist/lib/ansi.js +438 -0
  225. package/dist/lib/clock.d.ts +32 -0
  226. package/dist/lib/clock.js +106 -0
  227. package/dist/lib/cn.d.ts +12 -0
  228. package/dist/lib/cn.js +28 -0
  229. package/dist/lib/control-props.d.ts +21 -0
  230. package/dist/lib/control-props.js +14 -0
  231. package/dist/lib/direction.d.ts +13 -0
  232. package/dist/lib/direction.js +25 -0
  233. package/dist/lib/image-size.d.ts +12 -0
  234. package/dist/lib/image-size.js +66 -0
  235. package/dist/lib/keycaps.d.ts +21 -0
  236. package/dist/lib/keycaps.js +62 -0
  237. package/dist/lib/keyed.d.ts +9 -0
  238. package/dist/lib/keyed.js +14 -0
  239. package/dist/lib/keys.d.ts +49 -0
  240. package/dist/lib/keys.js +159 -0
  241. package/dist/lib/kit-motion.d.ts +9 -0
  242. package/dist/lib/kit-motion.js +12 -0
  243. package/dist/lib/link-check-dialog.d.ts +11 -0
  244. package/dist/lib/link-check-dialog.js +18 -0
  245. package/dist/lib/motion-features.d.ts +1 -0
  246. package/dist/lib/motion-features.js +4 -0
  247. package/dist/lib/motion-provider.d.ts +5 -0
  248. package/dist/lib/motion-provider.js +8 -0
  249. package/dist/lib/motion.d.ts +39 -0
  250. package/dist/lib/motion.js +49 -0
  251. package/dist/lib/number-format.d.ts +114 -0
  252. package/dist/lib/number-format.js +313 -0
  253. package/dist/lib/patch.d.ts +72 -0
  254. package/dist/lib/patch.js +344 -0
  255. package/dist/lib/path.d.ts +34 -0
  256. package/dist/lib/path.js +85 -0
  257. package/dist/lib/search.d.ts +10 -0
  258. package/dist/lib/search.js +20 -0
  259. package/dist/lib/shiki-themes.d.ts +7 -0
  260. package/dist/lib/shiki-themes.js +115 -0
  261. package/dist/lib/shiki.d.ts +101 -0
  262. package/dist/lib/shiki.js +353 -0
  263. package/dist/lib/shortcut-context.d.ts +23 -0
  264. package/dist/lib/shortcut-context.js +29 -0
  265. package/dist/lib/text.d.ts +56 -0
  266. package/dist/lib/text.js +149 -0
  267. package/dist/lib/theme-scope.d.ts +18 -0
  268. package/dist/lib/theme-scope.js +24 -0
  269. package/dist/lib/theme.d.ts +40 -0
  270. package/dist/lib/theme.js +96 -0
  271. package/dist/lib/tree-keys.d.ts +72 -0
  272. package/dist/lib/tree-keys.js +126 -0
  273. package/dist/lib/url.d.ts +17 -0
  274. package/dist/lib/url.js +38 -0
  275. package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
  276. package/dist/lib/use-copy-to-clipboard.js +40 -0
  277. package/dist/lib/use-delayed-loading.d.ts +11 -0
  278. package/dist/lib/use-delayed-loading.js +31 -0
  279. package/dist/lib/use-file-picker.d.ts +26 -0
  280. package/dist/lib/use-file-picker.js +43 -0
  281. package/dist/lib/use-hydrated.d.ts +6 -0
  282. package/dist/lib/use-hydrated.js +11 -0
  283. package/dist/lib/use-latest.d.ts +12 -0
  284. package/dist/lib/use-latest.js +22 -0
  285. package/dist/lib/use-resolved-theme.d.ts +12 -0
  286. package/dist/lib/use-resolved-theme.js +25 -0
  287. package/dist/styles/base.css +393 -0
  288. package/dist/styles/code.css +35 -0
  289. package/dist/styles/fonts.css +12 -0
  290. package/dist/styles/index.css +13 -0
  291. package/dist/styles/tokens.gen.css +494 -0
  292. package/package.json +208 -4
@@ -0,0 +1,344 @@
1
+ const HUNK_HEADER = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u;
2
+ const ANY_HUNK_HEADER = new RegExp(HUNK_HEADER.source, "mu");
3
+ const GIT_HEADER = "diff --git ";
4
+ const NO_NEWLINE = "\";
5
+ /** Whether `text` holds at least one `@@ -a,b +c,d @@` hunk. */
6
+ export function isUnifiedDiff(text) {
7
+ return ANY_HUNK_HEADER.test(text);
8
+ }
9
+ /* ------------------------------------------------------------------------------------------ */
10
+ /* Paths */
11
+ /* ------------------------------------------------------------------------------------------ */
12
+ const C_ESCAPES = {
13
+ a: "\u0007",
14
+ b: "\b",
15
+ f: "\f",
16
+ n: "\n",
17
+ r: "\r",
18
+ t: "\t",
19
+ v: "\v",
20
+ "\\": "\\",
21
+ '"': '"',
22
+ };
23
+ /**
24
+ * Git C-quotes paths with special characters (`"a/caf\303\251.txt"`). Octal escapes are UTF-8
25
+ * bytes, so they are collected and decoded together; anything else is written raw (`core.quotePath`
26
+ * off), taken a code point at a time so a character outside the BMP stays whole.
27
+ */
28
+ export function unquoteGitPath(raw) {
29
+ if (!(raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"'))) {
30
+ return raw;
31
+ }
32
+ const body = raw.slice(1, -1);
33
+ const bytes = [];
34
+ const encoder = new TextEncoder();
35
+ for (let i = 0, point = body.codePointAt(0); point !== undefined; point = body.codePointAt(i)) {
36
+ const char = String.fromCodePoint(point);
37
+ if (char !== "\\") {
38
+ bytes.push(...encoder.encode(char));
39
+ i += char.length;
40
+ continue;
41
+ }
42
+ const octal = /^[0-7]{3}/u.exec(body.slice(i + 1, i + 4));
43
+ if (octal !== null) {
44
+ bytes.push(Number.parseInt(octal[0], 8));
45
+ i += 4;
46
+ continue;
47
+ }
48
+ const next = body.charAt(i + 1);
49
+ bytes.push(...encoder.encode(C_ESCAPES[next] ?? next));
50
+ i += 2;
51
+ }
52
+ return new TextDecoder().decode(new Uint8Array(bytes));
53
+ }
54
+ /**
55
+ * `a/src/x.ts` → `src/x.ts`; `/dev/null` → undefined. A tab ends the path: `diff -u` puts its
56
+ * timestamp after one.
57
+ */
58
+ function stripSide(raw) {
59
+ const path = unquoteGitPath(raw.trim().replace(/\t.*$/u, ""));
60
+ if (path === "/dev/null") {
61
+ return undefined;
62
+ }
63
+ return path.replace(/^[ab]\//u, "");
64
+ }
65
+ const NO_PATHS = { oldPath: undefined, newPath: undefined };
66
+ /** Best effort for `diff --git a/x b/x` when no `---`/`+++`/rename lines name the file (binary, mode-only). */
67
+ function pathsFromDiffLine(line) {
68
+ if (!line.startsWith(GIT_HEADER)) {
69
+ return NO_PATHS;
70
+ }
71
+ const rest = line.slice(GIT_HEADER.length);
72
+ const quoted = /^("(?:[^"\\]|\\.)*"|\S+) ("(?:[^"\\]|\\.)*"|\S+)$/u.exec(rest);
73
+ if (quoted?.[1] !== undefined && quoted[2] !== undefined) {
74
+ return { oldPath: stripSide(quoted[1]), newPath: stripSide(quoted[2]) };
75
+ }
76
+ // Unquoted paths with spaces: both sides are equal unless renamed, so split in the middle.
77
+ const half = /^a\/(.+) b\/(.+)$/u.exec(rest);
78
+ if (half?.[1] !== undefined && half[2] !== undefined) {
79
+ return { oldPath: half[1], newPath: half[2] };
80
+ }
81
+ return NO_PATHS;
82
+ }
83
+ /* ------------------------------------------------------------------------------------------ */
84
+ /* Files */
85
+ /* ------------------------------------------------------------------------------------------ */
86
+ function withoutTrailingBlanks(lines) {
87
+ let end = lines.length;
88
+ while (end > 0 && lines[end - 1] === "")
89
+ end -= 1;
90
+ return lines.slice(0, end);
91
+ }
92
+ /** The patch's lines, CRLF read as LF, without the blank lines a patch often ends with. */
93
+ function patchLines(text) {
94
+ return withoutTrailingBlanks(text.replaceAll("\r\n", "\n").split("\n"));
95
+ }
96
+ /**
97
+ * Where each file starts: its `diff --git` line; in a patch without them (`diff -u`, hand-written),
98
+ * a `---` line, then `+++`, then a hunk. A removed `-- x` line before an added `++ y` one looks
99
+ * the same only at the end of a hunk with no trailing context, so a `---` line alone never splits.
100
+ * Bare hunks are one file.
101
+ */
102
+ function sectionStarts(lines) {
103
+ const git = lines.flatMap((line, index) => (line.startsWith(GIT_HEADER) ? [index] : []));
104
+ if (git.length > 0)
105
+ return git;
106
+ const unified = lines.flatMap((line, index) => line.startsWith("--- ") &&
107
+ (lines[index + 1] ?? "").startsWith("+++ ") &&
108
+ HUNK_HEADER.test(lines[index + 2] ?? "")
109
+ ? [index]
110
+ : []);
111
+ if (unified.length > 0)
112
+ return unified;
113
+ return lines.some((line) => HUNK_HEADER.test(line)) ? [0] : [];
114
+ }
115
+ /** The patch's files as runs of lines. Text before the first one (a commit message) belongs to none. */
116
+ function fileSections(lines) {
117
+ const starts = sectionStarts(lines);
118
+ return starts.map((start, index) => withoutTrailingBlanks(lines.slice(start, starts[index + 1])));
119
+ }
120
+ /** Reads one header line (before the first hunk) into `header`. */
121
+ function readHeaderLine(line, header) {
122
+ if (line.startsWith("--- ")) {
123
+ header.oldPath = stripSide(line.slice(4));
124
+ if (header.oldPath === undefined)
125
+ header.created = true;
126
+ }
127
+ else if (line.startsWith("+++ ")) {
128
+ header.newPath = stripSide(line.slice(4));
129
+ if (header.newPath === undefined)
130
+ header.deleted = true;
131
+ }
132
+ else if (line.startsWith("new file mode")) {
133
+ header.created = true;
134
+ }
135
+ else if (line.startsWith("deleted file mode")) {
136
+ header.deleted = true;
137
+ }
138
+ else if (line.startsWith("rename from ") || line.startsWith("copy from ")) {
139
+ header.renameFrom = unquoteGitPath(line.slice(line.indexOf(" from ") + 6));
140
+ }
141
+ else if (line.startsWith("rename to ") || line.startsWith("copy to ")) {
142
+ header.renameTo = unquoteGitPath(line.slice(line.indexOf(" to ") + 4));
143
+ }
144
+ else if (line.startsWith("Binary files ") || line === "GIT binary patch") {
145
+ header.binary = true;
146
+ }
147
+ }
148
+ /** Counts `+`/`-` lines after the first `@@`, where they can no longer be headers. */
149
+ function countChanges(lines) {
150
+ let additions = 0;
151
+ let deletions = 0;
152
+ for (const line of lines) {
153
+ if (line.startsWith("+")) {
154
+ additions += 1;
155
+ }
156
+ else if (line.startsWith("-")) {
157
+ deletions += 1;
158
+ }
159
+ }
160
+ return { additions, deletions };
161
+ }
162
+ function parseSection(lines) {
163
+ const firstHunk = lines.findIndex((line) => HUNK_HEADER.test(line));
164
+ const headerEnd = firstHunk === -1 ? lines.length : firstHunk;
165
+ const header = {};
166
+ for (const line of lines.slice(0, headerEnd)) {
167
+ readHeaderLine(line, header);
168
+ }
169
+ const fallback = pathsFromDiffLine(lines[0] ?? "");
170
+ const path = header.renameTo ?? header.newPath ?? header.oldPath ?? fallback.newPath ?? fallback.oldPath;
171
+ const previous = header.renameFrom ?? (header.renameTo === undefined ? undefined : fallback.oldPath);
172
+ return {
173
+ path,
174
+ oldPath: previous !== undefined && previous !== path ? previous : undefined,
175
+ change: header.created === true ? "added" : header.deleted === true ? "deleted" : "modified",
176
+ patch: lines.join("\n"),
177
+ ...countChanges(lines.slice(headerEnd)),
178
+ binary: header.binary === true,
179
+ };
180
+ }
181
+ /**
182
+ * Splits a patch (`git diff`, `diff -u`, or bare hunks) into one entry per file, with line counts.
183
+ * Text before the first file is ignored; text without hunks or file headers has no files.
184
+ */
185
+ export function splitPatch(patch) {
186
+ return fileSections(patchLines(patch)).map(parseSection);
187
+ }
188
+ /** `+`/`-` line counts across every file of a patch; file headers never count. */
189
+ export function patchStats(patch) {
190
+ let additions = 0;
191
+ let deletions = 0;
192
+ for (const file of splitPatch(patch)) {
193
+ additions += file.additions;
194
+ deletions += file.deletions;
195
+ }
196
+ return { additions, deletions };
197
+ }
198
+ /** A file's hunks. A bare empty body line is a context line whose leading space an editor stripped. */
199
+ function hunksOf(lines) {
200
+ const hunks = [];
201
+ for (const line of lines) {
202
+ const header = HUNK_HEADER.exec(line);
203
+ if (header !== null) {
204
+ hunks.push({ oldStart: Number(header[1]), newStart: Number(header[2]), header: line, body: [] });
205
+ }
206
+ else {
207
+ hunks.at(-1)?.body.push(line === "" ? " " : line);
208
+ }
209
+ }
210
+ return hunks;
211
+ }
212
+ const rawHunks = (patch) => fileSections(patchLines(patch)).flatMap(hunksOf);
213
+ /** The `@@` hunks of a patch, in order (the order DiffView indexes them). */
214
+ export function parseHunks(patch) {
215
+ return rawHunks(patch).map((hunk) => ({
216
+ oldStart: hunk.oldStart,
217
+ newStart: hunk.newStart,
218
+ text: [hunk.header, ...hunk.body].join("\n"),
219
+ key: hunk.body.join("\n"),
220
+ }));
221
+ }
222
+ /** Which sides a hunk body line occupies (` ` both, `-` old, `+` new). */
223
+ const LINE_SIDES = {
224
+ " ": { old: true, new: true },
225
+ "-": { old: true, new: false },
226
+ "+": { old: false, new: true },
227
+ };
228
+ /** The text of `line` on one side of a patch, when the patch shows it. */
229
+ export function lineText(patch, side, line) {
230
+ for (const hunk of rawHunks(patch)) {
231
+ const at = { old: hunk.oldStart, new: hunk.newStart };
232
+ for (const raw of hunk.body) {
233
+ const sides = LINE_SIDES[raw.charAt(0)];
234
+ if (sides === undefined)
235
+ continue;
236
+ if (sides[side] && at[side] === line)
237
+ return raw.slice(1);
238
+ if (sides.old)
239
+ at.old += 1;
240
+ if (sides.new)
241
+ at.new += 1;
242
+ }
243
+ }
244
+ return undefined;
245
+ }
246
+ /**
247
+ * Makes hand-written / model-written patches parse cleanly: CRLF → LF, no trailing newline, and
248
+ * bare empty lines inside hunks become the " " context lines they were meant to be (editors and
249
+ * LLMs routinely strip that leading space).
250
+ */
251
+ export function normalizePatch(patch) {
252
+ let inHunk = false;
253
+ return patchLines(patch)
254
+ .map((line) => {
255
+ if (line.startsWith("@@"))
256
+ inHunk = true;
257
+ else if (line.startsWith("diff "))
258
+ inHunk = false;
259
+ else if (inHunk && line === "")
260
+ return " ";
261
+ return line;
262
+ })
263
+ .join("\n");
264
+ }
265
+ /* ------------------------------------------------------------------------------------------ */
266
+ /* Building */
267
+ /* ------------------------------------------------------------------------------------------ */
268
+ /** A git-style patch of one file from its hunks' unified-diff text, so DiffView can render it. */
269
+ export function buildFilePatch(path, change, hunks) {
270
+ const head = [`diff --git a/${path} b/${path}`];
271
+ if (change === "added")
272
+ head.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
273
+ else if (change === "deleted")
274
+ head.push("deleted file mode 100644", `--- a/${path}`, "+++ /dev/null");
275
+ else
276
+ head.push(`--- a/${path}`, `+++ b/${path}`);
277
+ return [...head, ...hunks].join("\n");
278
+ }
279
+ /** A patch that adds `content` as a new file, so new files render like any other diff. */
280
+ export function newFilePatch(path, content) {
281
+ const lines = content === "" ? [] : content.replace(/\n$/u, "").split("\n");
282
+ if (lines.length === 0)
283
+ return buildFilePatch(path, "added", []);
284
+ const body = lines.map((line) => `+${line}`);
285
+ if (!content.endsWith("\n"))
286
+ body.push(NO_NEWLINE);
287
+ return buildFilePatch(path, "added", [[`@@ -0,0 +1,${lines.length} @@`, ...body].join("\n")]);
288
+ }
289
+ /* ------------------------------------------------------------------------------------------ */
290
+ /* Parsed diffs (@pierre/diffs) */
291
+ /* ------------------------------------------------------------------------------------------ */
292
+ /** Counts `+` / `−` lines across all hunks. */
293
+ export function getDiffStats(diff) {
294
+ let additions = 0;
295
+ let deletions = 0;
296
+ for (const hunk of diff.hunks) {
297
+ additions += hunk.additionLines;
298
+ deletions += hunk.deletionLines;
299
+ }
300
+ return { additions, deletions };
301
+ }
302
+ const stripEol = (line) => (line ?? "").replace(/\r?\n$/u, "");
303
+ /**
304
+ * One hunk as unified-diff text (`@@ … @@` header plus ` `/`-`/`+` lines and the no-newline
305
+ * marker), e.g. to quote in a message or to compare with a patch's hunk.
306
+ */
307
+ export function getHunkExcerpt(diff, hunkIndex) {
308
+ const hunk = diff.hunks[hunkIndex];
309
+ if (!hunk)
310
+ return "";
311
+ const context = hunk.hunkContext?.trim();
312
+ const out = [
313
+ `@@ -${hunk.deletionStart},${hunk.deletionCount} +${hunk.additionStart},${hunk.additionCount} @@${context ? ` ${context}` : ""}`,
314
+ ];
315
+ // Where each side's last line ends: git's no-newline marker follows that line.
316
+ let oldEnd = 0;
317
+ let newEnd = 0;
318
+ for (const block of hunk.hunkContent) {
319
+ if (block.type === "context") {
320
+ for (let i = 0; i < block.lines; i++)
321
+ out.push(` ${stripEol(diff.additionLines[block.additionLineIndex + i])}`);
322
+ if (block.lines > 0) {
323
+ oldEnd = out.length;
324
+ newEnd = out.length;
325
+ }
326
+ continue;
327
+ }
328
+ for (let i = 0; i < block.deletions; i++)
329
+ out.push(`-${stripEol(diff.deletionLines[block.deletionLineIndex + i])}`);
330
+ if (block.deletions > 0)
331
+ oldEnd = out.length;
332
+ for (let i = 0; i < block.additions; i++)
333
+ out.push(`+${stripEol(diff.additionLines[block.additionLineIndex + i])}`);
334
+ if (block.additions > 0)
335
+ newEnd = out.length;
336
+ }
337
+ const markers = new Set([
338
+ ...(hunk.noEOFCRDeletions && oldEnd > 0 ? [oldEnd] : []),
339
+ ...(hunk.noEOFCRAdditions && newEnd > 0 ? [newEnd] : []),
340
+ ]);
341
+ for (const at of [...markers].toSorted((a, b) => b - a))
342
+ out.splice(at, 0, NO_NEWLINE);
343
+ return out.join("\n");
344
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Paths as people read them, from any machine: a daemon on Windows writes `\`, so each helper takes
3
+ * either separator and keeps the one it found. `@nibapp/ui/path`, a preview API: it may change in a
4
+ * minor version before 1.0.
5
+ */
6
+ /**
7
+ * `src/auth/login.ts` → `["src/auth/", "login.ts"]`: the folder with its separator, then the name.
8
+ * Either separator; a trailing separator doesn't count (a lone root stays the folder).
9
+ */
10
+ export declare function splitPath(path: string): readonly [dir: string, name: string];
11
+ /** A path's last segment, either separator; a trailing separator doesn't count. */
12
+ export declare function basename(path: string): string;
13
+ /**
14
+ * Everything before a path's last segment, separators as they were: "" for a bare name, the root
15
+ * itself for a file at a root (`/a` → `/`, `C:\a` → `C:\`); a trailing separator doesn't count.
16
+ */
17
+ export declare function dirname(path: string): string;
18
+ /** Whether a path is absolute on any machine: `/…`, `C:\…` or `C:/…`, or a UNC `\\server\share`. */
19
+ export declare function isAbsolutePath(path: string): boolean;
20
+ /**
21
+ * A relative `path` against `root`, joined with the separator `root` writes and no doubled one
22
+ * (`C:\proj` + `src/a.ts` → `C:\proj\src\a.ts`); an absolute or `~` path stays as it is.
23
+ */
24
+ export declare function joinPath(root: string, path: string): string;
25
+ /**
26
+ * A path with `.` and `..` resolved and `/` between its segments, its drive letter upper-cased, so
27
+ * two spellings of one path compare equal (`c:\proj\.\src\..\a.ts` → `C:/proj/a.ts`).
28
+ */
29
+ export declare function normalizePath(path: string): string;
30
+ /**
31
+ * `path` relative to `root` when it lies under it, either separator and either drive-letter case,
32
+ * written with the separator `path` uses; undefined outside it, or for `root` itself.
33
+ */
34
+ export declare function pathUnder(path: string, root: string): string | undefined;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Paths as people read them, from any machine: a daemon on Windows writes `\`, so each helper takes
3
+ * either separator and keeps the one it found. `@nibapp/ui/path`, a preview API: it may change in a
4
+ * minor version before 1.0.
5
+ */
6
+ /**
7
+ * `src/auth/login.ts` → `["src/auth/", "login.ts"]`: the folder with its separator, then the name.
8
+ * Either separator; a trailing separator doesn't count (a lone root stays the folder).
9
+ */
10
+ export function splitPath(path) {
11
+ const trimmed = path.replace(/(.)[/\\]+$/u, "$1");
12
+ const cut = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")) + 1;
13
+ return [trimmed.slice(0, cut), trimmed.slice(cut)];
14
+ }
15
+ /** A path's last segment, either separator; a trailing separator doesn't count. */
16
+ export function basename(path) {
17
+ return splitPath(path)[1];
18
+ }
19
+ /** A path's root alone: `/`, `\`, `C:\` or `C:/`. */
20
+ const ROOT = /^(?:[/\\]|[A-Za-z]:[/\\])$/u;
21
+ /**
22
+ * Everything before a path's last segment, separators as they were: "" for a bare name, the root
23
+ * itself for a file at a root (`/a` → `/`, `C:\a` → `C:\`); a trailing separator doesn't count.
24
+ */
25
+ export function dirname(path) {
26
+ const [dir] = splitPath(path);
27
+ return ROOT.test(dir) ? dir : dir.replace(/[/\\]+$/u, "");
28
+ }
29
+ /** Whether a path is absolute on any machine: `/…`, `C:\…` or `C:/…`, or a UNC `\\server\share`. */
30
+ export function isAbsolutePath(path) {
31
+ return path.startsWith("/") || path.startsWith("\\\\") || /^[A-Za-z]:[/\\]/u.test(path);
32
+ }
33
+ /** The separator a path writes: `\` when it has backslashes and no `/`, else `/`. */
34
+ function separatorOf(path) {
35
+ return path.includes("\\") && !path.includes("/") ? "\\" : "/";
36
+ }
37
+ /**
38
+ * A relative `path` against `root`, joined with the separator `root` writes and no doubled one
39
+ * (`C:\proj` + `src/a.ts` → `C:\proj\src\a.ts`); an absolute or `~` path stays as it is.
40
+ */
41
+ export function joinPath(root, path) {
42
+ if (isAbsolutePath(path) || path.startsWith("~"))
43
+ return path;
44
+ const separator = separatorOf(root);
45
+ const rest = path.replace(/^\.[/\\]/u, "").replace(/^[/\\]+/u, "");
46
+ const base = root.replace(/(.)[/\\]+$/u, "$1");
47
+ if (rest === "")
48
+ return base;
49
+ const joined = separator === "\\" ? rest.replaceAll("/", "\\") : rest;
50
+ return base.endsWith(separator) ? `${base}${joined}` : `${base}${separator}${joined}`;
51
+ }
52
+ /**
53
+ * A path with `.` and `..` resolved and `/` between its segments, its drive letter upper-cased, so
54
+ * two spellings of one path compare equal (`c:\proj\.\src\..\a.ts` → `C:/proj/a.ts`).
55
+ */
56
+ export function normalizePath(path) {
57
+ const unc = path.startsWith("\\\\") || path.startsWith("//");
58
+ const drive = /^([A-Za-z]):/u.exec(path)?.[1];
59
+ const rest = drive === undefined ? path : path.slice(2);
60
+ const rooted = unc || /^[/\\]/u.test(rest);
61
+ const segments = [];
62
+ for (const segment of rest.split(/[/\\]+/u)) {
63
+ if (segment === "" || segment === ".")
64
+ continue;
65
+ if (segment === "..")
66
+ segments.pop();
67
+ else
68
+ segments.push(segment);
69
+ }
70
+ const head = unc ? "//" : `${drive === undefined ? "" : `${drive.toUpperCase()}:`}${rooted ? "/" : ""}`;
71
+ return `${head}${segments.join("/")}`;
72
+ }
73
+ /**
74
+ * `path` relative to `root` when it lies under it, either separator and either drive-letter case,
75
+ * written with the separator `path` uses; undefined outside it, or for `root` itself.
76
+ */
77
+ export function pathUnder(path, root) {
78
+ const base = normalizePath(root);
79
+ const prefix = base.endsWith("/") ? base : `${base}/`;
80
+ const full = normalizePath(path);
81
+ if (!full.startsWith(prefix) || full.length === prefix.length)
82
+ return undefined;
83
+ const relative = full.slice(prefix.length);
84
+ return separatorOf(path) === "\\" ? relative.replaceAll("/", "\\") : relative;
85
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * How every search box compares text: one rule, so "é", "ё" and full-width
3
+ * letters match the same way in the palette, Settings, the provider directory and the model picker.
4
+ */
5
+ /** Lower-case, compatibility-decomposed, without combining marks ("Émoji" → "emoji", "ёлка" → "елка"), trimmed. */
6
+ export declare function normalizeForSearch(text: string): string;
7
+ /** The query's normalised terms; empty for a blank query. */
8
+ export declare function searchTerms(query: string): string[];
9
+ /** Every term of `query` appears in `haystack`; a blank query matches everything. */
10
+ export declare function matchesTerms(haystack: string, query: string): boolean;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * How every search box compares text: one rule, so "é", "ё" and full-width
3
+ * letters match the same way in the palette, Settings, the provider directory and the model picker.
4
+ */
5
+ /** Lower-case, compatibility-decomposed, without combining marks ("Émoji" → "emoji", "ёлка" → "елка"), trimmed. */
6
+ export function normalizeForSearch(text) {
7
+ return text.toLowerCase().normalize("NFKD").replace(/\p{M}/gu, "").trim();
8
+ }
9
+ /** The query's normalised terms; empty for a blank query. */
10
+ export function searchTerms(query) {
11
+ return normalizeForSearch(query).split(/\s+/u).filter(Boolean);
12
+ }
13
+ /** Every term of `query` appears in `haystack`; a blank query matches everything. */
14
+ export function matchesTerms(haystack, query) {
15
+ const terms = searchTerms(query);
16
+ if (terms.length === 0)
17
+ return true;
18
+ const text = normalizeForSearch(haystack);
19
+ return terms.every((term) => text.includes(term));
20
+ }
@@ -0,0 +1,7 @@
1
+ import type { ThemeRegistration } from "shiki/types";
2
+ export declare const nibLightTheme: ThemeRegistration;
3
+ export declare const nibDarkTheme: ThemeRegistration;
4
+ export declare const NIB_SHIKI_THEMES: {
5
+ readonly light: "kit-light";
6
+ readonly dark: "kit-dark";
7
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The kit's syntax themes for Shiki (`kit-light` / `kit-dark`). Their colours are tokens: each role's
3
+ * ramp source is the token spec's `SYNTAX`, and the kit's token generator holds every one to 4.5:1 on
4
+ * each background code sits on (its `syntax`). Both themes share font styles
5
+ * so a single `--shiki-light-font-style` var is enough for either scheme. They're named for the kit's
6
+ * themes, Paper and Graphite.
7
+ */
8
+ import { palette, syntax } from "../_bundled/tokens/index.js";
9
+ const light = { ...syntax.light, bg: palette.light.gray[0] };
10
+ const dark = { ...syntax.dark, bg: palette.dark.gray[0] };
11
+ const rule = (scope, foreground, fontStyle) => ({
12
+ scope,
13
+ settings: fontStyle === undefined ? { foreground } : { foreground, fontStyle },
14
+ });
15
+ function buildTheme(name, type, p) {
16
+ return {
17
+ name,
18
+ displayName: type === "light" ? "Paper" : "Graphite",
19
+ type,
20
+ fg: p.fg,
21
+ bg: p.bg,
22
+ colors: { "editor.foreground": p.fg, "editor.background": p.bg },
23
+ settings: [
24
+ { settings: { foreground: p.fg, background: p.bg } },
25
+ rule(["comment", "punctuation.definition.comment", "string.comment"], p.comment, "italic"),
26
+ rule([
27
+ "keyword",
28
+ "storage",
29
+ "storage.type",
30
+ "storage.modifier",
31
+ "keyword.control",
32
+ "keyword.operator.new",
33
+ "keyword.operator.expression",
34
+ "keyword.operator.logical.python",
35
+ "variable.language",
36
+ "support.type.primitive",
37
+ ], p.keyword),
38
+ rule([
39
+ "keyword.operator",
40
+ "punctuation",
41
+ "meta.brace",
42
+ "punctuation.separator",
43
+ "punctuation.terminator",
44
+ "punctuation.accessor",
45
+ "storage.type.function.arrow",
46
+ ], p.muted),
47
+ rule([
48
+ "string",
49
+ "string.quoted",
50
+ "string.template",
51
+ "punctuation.definition.string",
52
+ "markup.inline.raw",
53
+ "markup.raw",
54
+ ], p.string),
55
+ rule([
56
+ "constant.numeric",
57
+ "constant.language",
58
+ "constant.character",
59
+ "constant.other",
60
+ "variable.other.enummember",
61
+ "support.constant",
62
+ "constant.character.escape",
63
+ "keyword.other.unit",
64
+ ], p.number),
65
+ rule([
66
+ "entity.name.function",
67
+ "support.function",
68
+ "meta.function-call entity.name.function",
69
+ "variable.function",
70
+ "entity.name.command",
71
+ "support.type.property-name.json",
72
+ "entity.name.tag.yaml",
73
+ "markup.heading",
74
+ "markup.underline.link",
75
+ "string.other.link",
76
+ ], p.fn),
77
+ rule([
78
+ "entity.name.type",
79
+ "entity.name.class",
80
+ "entity.name.namespace",
81
+ "entity.other.inherited-class",
82
+ "support.type",
83
+ "support.class",
84
+ "storage.type.cs",
85
+ "storage.type.java",
86
+ "entity.name.type.parameter",
87
+ ], p.type),
88
+ rule(["entity.name.tag", "meta.tag.sgml", "punctuation.definition.tag.begin.html"], p.tag),
89
+ rule([
90
+ "entity.other.attribute-name",
91
+ "entity.other.attribute-name.class.css",
92
+ "support.type.vendored.property-name",
93
+ "support.constant.property-value",
94
+ "meta.decorator",
95
+ "punctuation.decorator",
96
+ ], p.attribute),
97
+ rule(["string.regexp", "constant.other.character-class.regexp"], p.regex),
98
+ rule(["variable", "variable.other", "variable.parameter", "meta.object-literal.key"], p.fg),
99
+ rule(["support.type.property-name.css", "support.type.property-name"], p.fg),
100
+ rule(["markup.bold"], p.fg, "bold"),
101
+ rule(["markup.italic"], p.fg, "italic"),
102
+ rule(["markup.strikethrough"], p.muted, "strikethrough"),
103
+ rule(["markup.quote"], p.muted, "italic"),
104
+ rule(["markup.list", "punctuation.definition.list"], p.muted),
105
+ rule(["markup.inserted", "punctuation.definition.inserted"], p.inserted),
106
+ rule(["markup.deleted", "punctuation.definition.deleted"], p.deleted),
107
+ rule(["markup.changed", "punctuation.definition.changed"], p.number),
108
+ rule(["meta.diff.range", "meta.diff.header", "meta.diff.index"], p.keyword),
109
+ rule(["invalid", "invalid.illegal"], p.deleted),
110
+ ],
111
+ };
112
+ }
113
+ export const nibLightTheme = buildTheme("kit-light", "light", light);
114
+ export const nibDarkTheme = buildTheme("kit-dark", "dark", dark);
115
+ export const NIB_SHIKI_THEMES = { light: "kit-light", dark: "kit-dark" };