@abelfubu/dv 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 (118) hide show
  1. package/dist/ansi-html.d.ts +42 -0
  2. package/dist/ansi-html.d.ts.map +1 -0
  3. package/dist/ansi-html.js +327 -0
  4. package/dist/ansi-output.d.ts +22 -0
  5. package/dist/ansi-output.d.ts.map +1 -0
  6. package/dist/ansi-output.js +154 -0
  7. package/dist/balance-delimiters.d.ts +25 -0
  8. package/dist/balance-delimiters.d.ts.map +1 -0
  9. package/dist/balance-delimiters.js +539 -0
  10. package/dist/balance-delimiters.test.d.ts +2 -0
  11. package/dist/balance-delimiters.test.d.ts.map +1 -0
  12. package/dist/balance-delimiters.test.js +1029 -0
  13. package/dist/cli-copy-notification.test.d.ts +2 -0
  14. package/dist/cli-copy-notification.test.d.ts.map +1 -0
  15. package/dist/cli-copy-notification.test.js +80 -0
  16. package/dist/cli-scroll.test.d.ts +2 -0
  17. package/dist/cli-scroll.test.d.ts.map +1 -0
  18. package/dist/cli-scroll.test.js +283 -0
  19. package/dist/cli.d.ts +9 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +976 -0
  22. package/dist/clipboard.d.ts +16 -0
  23. package/dist/clipboard.d.ts.map +1 -0
  24. package/dist/clipboard.js +128 -0
  25. package/dist/components/diff-view.d.ts +32 -0
  26. package/dist/components/diff-view.d.ts.map +1 -0
  27. package/dist/components/diff-view.js +123 -0
  28. package/dist/components/diff-view.test.d.ts +5 -0
  29. package/dist/components/diff-view.test.d.ts.map +1 -0
  30. package/dist/components/diff-view.test.js +312 -0
  31. package/dist/components/directory-tree-view.d.ts +33 -0
  32. package/dist/components/directory-tree-view.d.ts.map +1 -0
  33. package/dist/components/directory-tree-view.js +262 -0
  34. package/dist/components/index.d.ts +4 -0
  35. package/dist/components/index.d.ts.map +1 -0
  36. package/dist/components/index.js +5 -0
  37. package/dist/components/toast.d.ts +21 -0
  38. package/dist/components/toast.d.ts.map +1 -0
  39. package/dist/components/toast.js +47 -0
  40. package/dist/diff-cursor-utils.d.ts +20 -0
  41. package/dist/diff-cursor-utils.d.ts.map +1 -0
  42. package/dist/diff-cursor-utils.js +105 -0
  43. package/dist/diff-cursor-utils.test.d.ts +2 -0
  44. package/dist/diff-cursor-utils.test.d.ts.map +1 -0
  45. package/dist/diff-cursor-utils.test.js +40 -0
  46. package/dist/diff-surface-copy.d.ts +23 -0
  47. package/dist/diff-surface-copy.d.ts.map +1 -0
  48. package/dist/diff-surface-copy.js +64 -0
  49. package/dist/diff-surface-copy.test.d.ts +5 -0
  50. package/dist/diff-surface-copy.test.d.ts.map +1 -0
  51. package/dist/diff-surface-copy.test.js +142 -0
  52. package/dist/diff-utils.d.ts +196 -0
  53. package/dist/diff-utils.d.ts.map +1 -0
  54. package/dist/diff-utils.js +682 -0
  55. package/dist/diff-utils.test.d.ts +2 -0
  56. package/dist/diff-utils.test.d.ts.map +1 -0
  57. package/dist/diff-utils.test.js +727 -0
  58. package/dist/directory-tree.d.ts +72 -0
  59. package/dist/directory-tree.d.ts.map +1 -0
  60. package/dist/directory-tree.js +161 -0
  61. package/dist/directory-tree.test.d.ts +2 -0
  62. package/dist/directory-tree.test.d.ts.map +1 -0
  63. package/dist/directory-tree.test.js +383 -0
  64. package/dist/dropdown.d.ts +26 -0
  65. package/dist/dropdown.d.ts.map +1 -0
  66. package/dist/dropdown.js +172 -0
  67. package/dist/dropdown.test.d.ts +2 -0
  68. package/dist/dropdown.test.d.ts.map +1 -0
  69. package/dist/dropdown.test.js +106 -0
  70. package/dist/filter-submodule.e2e.test.d.ts +2 -0
  71. package/dist/filter-submodule.e2e.test.d.ts.map +1 -0
  72. package/dist/filter-submodule.e2e.test.js +109 -0
  73. package/dist/hooks/use-copy-selection.d.ts +29 -0
  74. package/dist/hooks/use-copy-selection.d.ts.map +1 -0
  75. package/dist/hooks/use-copy-selection.js +46 -0
  76. package/dist/kv-codec.d.ts +16 -0
  77. package/dist/kv-codec.d.ts.map +1 -0
  78. package/dist/kv-codec.js +36 -0
  79. package/dist/license.d.ts +14 -0
  80. package/dist/license.d.ts.map +1 -0
  81. package/dist/license.js +63 -0
  82. package/dist/logger.d.ts +9 -0
  83. package/dist/logger.d.ts.map +1 -0
  84. package/dist/logger.js +78 -0
  85. package/dist/monochrome.d.ts +34 -0
  86. package/dist/monochrome.d.ts.map +1 -0
  87. package/dist/monochrome.js +613 -0
  88. package/dist/monotone.d.ts +22 -0
  89. package/dist/monotone.d.ts.map +1 -0
  90. package/dist/monotone.js +185 -0
  91. package/dist/parsers-config.d.ts +19 -0
  92. package/dist/parsers-config.d.ts.map +1 -0
  93. package/dist/parsers-config.js +271 -0
  94. package/dist/patch-terminal-dimensions.d.ts +2 -0
  95. package/dist/patch-terminal-dimensions.d.ts.map +1 -0
  96. package/dist/patch-terminal-dimensions.js +45 -0
  97. package/dist/stdin-pager.test.d.ts +2 -0
  98. package/dist/stdin-pager.test.d.ts.map +1 -0
  99. package/dist/stdin-pager.test.js +497 -0
  100. package/dist/store.d.ts +16 -0
  101. package/dist/store.d.ts.map +1 -0
  102. package/dist/store.js +48 -0
  103. package/dist/themes/github.json +247 -0
  104. package/dist/themes.d.ts +59 -0
  105. package/dist/themes.d.ts.map +1 -0
  106. package/dist/themes.js +248 -0
  107. package/dist/tree-icons.d.ts +4 -0
  108. package/dist/tree-icons.d.ts.map +1 -0
  109. package/dist/tree-icons.js +18 -0
  110. package/dist/utils.d.ts +2 -0
  111. package/dist/utils.d.ts.map +1 -0
  112. package/dist/utils.js +13 -0
  113. package/dist/web-utils.d.ts +56 -0
  114. package/dist/web-utils.d.ts.map +1 -0
  115. package/dist/web-utils.js +363 -0
  116. package/package.json +37 -0
  117. package/public/jetbrains-mono-nerd.ttf +0 -0
  118. package/public/jetbrains-mono-nerd.woff2 +0 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-copy-notification.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-copy-notification.test.d.ts","sourceRoot":"","sources":["../src/cli-copy-notification.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx } from "@opentuah/react/jsx-runtime";
2
+ // Tests for the transient copy notification shown after pressing `y`.
3
+ import * as React from "react";
4
+ import { afterEach, describe, expect, it } from "bun:test";
5
+ import { act } from "react";
6
+ import { testRender } from "@opentuah/react/test-utils";
7
+ import { App } from "./cli.js";
8
+ const sampleDiff = `diff --git a/a.ts b/a.ts
9
+ index 1111111..2222222 100644
10
+ --- a/a.ts
11
+ +++ b/a.ts
12
+ @@ -1,4 +1,4 @@
13
+ context
14
+ -old
15
+ +new
16
+ -more
17
+ +more-new
18
+ keep
19
+ `;
20
+ function createParsedFile(path) {
21
+ return {
22
+ oldFileName: `a/${path}`,
23
+ newFileName: `b/${path}`,
24
+ hunks: [{ lines: [" context", "-old", "+new", "-more", "+more-new", " keep"] }],
25
+ rawDiff: sampleDiff,
26
+ };
27
+ }
28
+ async function renderUntilStable(testSetup, times = 8) {
29
+ for (let index = 0; index < times; index++) {
30
+ await new Promise((resolve) => setTimeout(resolve, 50));
31
+ await testSetup.renderOnce();
32
+ }
33
+ }
34
+ describe("App copy notification", () => {
35
+ let testSetup;
36
+ afterEach(() => {
37
+ if (testSetup) {
38
+ act(() => {
39
+ testSetup.renderer.destroy();
40
+ });
41
+ testSetup = undefined;
42
+ }
43
+ });
44
+ it("shows a transient success notification after copying selected lines", async () => {
45
+ const parsedFiles = [createParsedFile("a.ts")];
46
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
47
+ width: 120,
48
+ height: 16,
49
+ });
50
+ await renderUntilStable(testSetup);
51
+ // Focus diff pane, start selection, extend by one line, then copy.
52
+ await act(async () => {
53
+ testSetup.mockInput.pressTab();
54
+ await testSetup.renderOnce();
55
+ });
56
+ await act(async () => {
57
+ testSetup.mockInput.pressKey("v");
58
+ await testSetup.renderOnce();
59
+ });
60
+ await act(async () => {
61
+ testSetup.mockInput.pressKey("j");
62
+ await testSetup.renderOnce();
63
+ });
64
+ await act(async () => {
65
+ testSetup.mockInput.pressKey("y");
66
+ await testSetup.renderOnce();
67
+ });
68
+ const frame = testSetup.captureCharFrame();
69
+ expect(frame).toContain("Copied 2 lines");
70
+ expect(frame).toContain("✓");
71
+ // Wait for the auto-hide timeout to clear the notification.
72
+ await new Promise((resolve) => setTimeout(resolve, 2500));
73
+ await act(async () => {
74
+ await testSetup.renderOnce();
75
+ });
76
+ const clearedFrame = testSetup.captureCharFrame();
77
+ expect(clearedFrame).not.toContain("Copied 2 lines");
78
+ expect(clearedFrame).not.toContain("✓");
79
+ });
80
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-scroll.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-scroll.test.d.ts","sourceRoot":"","sources":["../src/cli-scroll.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,283 @@
1
+ import { jsx as _jsx } from "@opentuah/react/jsx-runtime";
2
+ // Tests mouse-wheel scrolling behavior in the main diff App scrollbox.
3
+ import * as React from "react";
4
+ import { afterEach, describe, expect, it } from "bun:test";
5
+ import { act } from "react";
6
+ import { testRender } from "@opentuah/react/test-utils";
7
+ import { App } from "./cli.js";
8
+ function createParsedFile(path, index, lineCount = 1) {
9
+ const lines = Array.from({ length: lineCount }, (_, i) => `+line${i}`);
10
+ return {
11
+ oldFileName: path,
12
+ newFileName: path,
13
+ hunks: [{ lines }],
14
+ rawDiff: [
15
+ `diff --git a/${path} b/${path}`,
16
+ `--- ${path}`,
17
+ `+++ ${path}`,
18
+ `@@ -0,0 +1,${lineCount} @@`,
19
+ ...lines,
20
+ ].join("\n"),
21
+ };
22
+ }
23
+ describe("App scrollbox", () => {
24
+ let testSetup;
25
+ afterEach(() => {
26
+ if (testSetup) {
27
+ act(() => {
28
+ testSetup.renderer.destroy();
29
+ });
30
+ }
31
+ });
32
+ it("renders the sidebar navigator beside the diff document", async () => {
33
+ const parsedFiles = [
34
+ createParsedFile("README.md", 0),
35
+ createParsedFile("src/file-01.ts", 1),
36
+ ];
37
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
38
+ width: 120,
39
+ height: 12,
40
+ });
41
+ await act(async () => {
42
+ await testSetup.renderOnce();
43
+ });
44
+ const frame = testSetup.captureCharFrame();
45
+ const lines = frame.split("\n");
46
+ // Sidebar tree content should be visible
47
+ expect(lines[1]).toContain("README.md (+1)");
48
+ expect(lines[2]).toContain("src");
49
+ expect(lines[3]).toContain("file-01.ts (+1)");
50
+ // Diff content should be visible somewhere in the frame
51
+ expect(frame).toContain("line0");
52
+ });
53
+ it("shows only the current file in diff pane", async () => {
54
+ const parsedFiles = [
55
+ createParsedFile("README.md", 0),
56
+ createParsedFile("src/file-01.ts", 1),
57
+ createParsedFile("src/file-02.ts", 2),
58
+ ];
59
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
60
+ width: 120,
61
+ height: 12,
62
+ });
63
+ await act(async () => {
64
+ await testSetup.renderOnce();
65
+ });
66
+ const frame = testSetup.captureCharFrame();
67
+ // Should show first file's content
68
+ expect(frame).toContain("line0");
69
+ // Should NOT show other files' content
70
+ expect(frame).not.toContain("file-01.ts +1-0");
71
+ expect(frame).not.toContain("file-02.ts +1-0");
72
+ });
73
+ it("toggles sidebar visibility with b key", async () => {
74
+ const parsedFiles = [
75
+ createParsedFile("README.md", 0),
76
+ createParsedFile("src/file-01.ts", 1),
77
+ ];
78
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
79
+ width: 120,
80
+ height: 12,
81
+ });
82
+ await act(async () => {
83
+ await testSetup.renderOnce();
84
+ });
85
+ // Sidebar is visible initially
86
+ const before = testSetup.captureCharFrame();
87
+ expect(before).toContain("README.md (+1)");
88
+ // Press b to hide sidebar
89
+ testSetup.mockInput.pressKey("b");
90
+ await new Promise((r) => setTimeout(r, 10));
91
+ await act(async () => {
92
+ await testSetup.renderOnce();
93
+ });
94
+ const hidden = testSetup.captureCharFrame();
95
+ // Sidebar content should no longer be visible in the frame
96
+ expect(hidden).not.toContain("README.md (+1)");
97
+ // Press b again to show sidebar
98
+ testSetup.mockInput.pressKey("b");
99
+ await new Promise((r) => setTimeout(r, 10));
100
+ await act(async () => {
101
+ await testSetup.renderOnce();
102
+ });
103
+ const shown = testSetup.captureCharFrame();
104
+ expect(shown).toContain("README.md (+1)");
105
+ });
106
+ it("does not reopen sidebar when file picker selects a file", async () => {
107
+ const parsedFiles = [
108
+ createParsedFile("README.md", 0),
109
+ createParsedFile("src/file-01.ts", 1),
110
+ ];
111
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
112
+ width: 120,
113
+ height: 12,
114
+ });
115
+ await act(async () => {
116
+ await testSetup.renderOnce();
117
+ });
118
+ // Hide sidebar
119
+ testSetup.mockInput.pressKey("b");
120
+ await new Promise((r) => setTimeout(r, 10));
121
+ await act(async () => {
122
+ await testSetup.renderOnce();
123
+ });
124
+ const hidden = testSetup.captureCharFrame();
125
+ expect(hidden).not.toContain("README.md (+1)");
126
+ // Open file picker
127
+ testSetup.mockInput.pressKey("p");
128
+ await new Promise((r) => setTimeout(r, 10));
129
+ await act(async () => {
130
+ await testSetup.renderOnce();
131
+ });
132
+ // Dropdown should be visible
133
+ const withDropdown = testSetup.captureCharFrame();
134
+ expect(withDropdown).toContain("Search files...");
135
+ // Select first option (README.md) with Enter
136
+ testSetup.mockInput.pressEnter();
137
+ await new Promise((r) => setTimeout(r, 10));
138
+ await act(async () => {
139
+ await testSetup.renderOnce();
140
+ });
141
+ // Dropdown should close and sidebar should remain hidden
142
+ const afterSelect = testSetup.captureCharFrame();
143
+ expect(afterSelect).not.toContain("Search files...");
144
+ expect(afterSelect).not.toContain("README.md (+1)");
145
+ });
146
+ it("navigates files with j/k when sidebar is focused", async () => {
147
+ const parsedFiles = [
148
+ createParsedFile("README.md", 0),
149
+ createParsedFile("src/file-01.ts", 1),
150
+ createParsedFile("src/file-02.ts", 2),
151
+ ];
152
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
153
+ width: 120,
154
+ height: 12,
155
+ });
156
+ await act(async () => {
157
+ await testSetup.renderOnce();
158
+ });
159
+ const initialFrame = testSetup.captureCharFrame();
160
+ expect(initialFrame).toContain("line0");
161
+ expect(initialFrame).not.toContain("Select theme");
162
+ // Sidebar is focused by default, press j to move to next file
163
+ testSetup.mockInput.pressKey("j");
164
+ await new Promise((r) => setTimeout(r, 50));
165
+ await act(async () => {
166
+ await testSetup.renderOnce();
167
+ });
168
+ const afterJ = testSetup.captureCharFrame();
169
+ // Should show file 1's header
170
+ expect(afterJ).toContain("file-01.ts");
171
+ // Press k to move back to previous file
172
+ testSetup.mockInput.pressKey("k");
173
+ await new Promise((r) => setTimeout(r, 50));
174
+ await act(async () => {
175
+ await testSetup.renderOnce();
176
+ });
177
+ const afterK = testSetup.captureCharFrame();
178
+ // Should show file 0's header
179
+ expect(afterK).toContain("README.md");
180
+ });
181
+ it("scrolls within single file with j/k when diff pane is focused", async () => {
182
+ // Create a single file with many lines
183
+ const parsedFiles = [
184
+ createParsedFile("src/large-file.ts", 0, 20),
185
+ ];
186
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
187
+ width: 120,
188
+ height: 12,
189
+ });
190
+ await act(async () => {
191
+ await testSetup.renderOnce();
192
+ });
193
+ const before = testSetup.captureCharFrame();
194
+ expect(before).toContain("line0");
195
+ // Switch to diff pane focus
196
+ testSetup.mockInput.pressTab();
197
+ await new Promise((r) => setTimeout(r, 10));
198
+ // Press j several times to scroll down within the file
199
+ for (let i = 0; i < 5; i++) {
200
+ testSetup.mockInput.pressKey("j");
201
+ await new Promise((r) => setTimeout(r, 5));
202
+ }
203
+ await act(async () => {
204
+ await testSetup.renderOnce();
205
+ });
206
+ const afterJ = testSetup.captureCharFrame();
207
+ // After scrolling, frame should have changed
208
+ expect(afterJ).not.toBe(before);
209
+ });
210
+ it("file picker works after tab focus switch", async () => {
211
+ const parsedFiles = [
212
+ createParsedFile("README.md", 0),
213
+ createParsedFile("src/file-01.ts", 1),
214
+ ];
215
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
216
+ width: 120,
217
+ height: 12,
218
+ });
219
+ await act(async () => {
220
+ await testSetup.renderOnce();
221
+ });
222
+ // Press tab to switch focus, then p to open picker
223
+ testSetup.mockInput.pressTab();
224
+ await new Promise((r) => setTimeout(r, 10));
225
+ testSetup.mockInput.pressKey("p");
226
+ await new Promise((r) => setTimeout(r, 10));
227
+ await act(async () => {
228
+ await testSetup.renderOnce();
229
+ });
230
+ // Press escape to close
231
+ testSetup.mockInput.pressEscape();
232
+ await new Promise((r) => setTimeout(r, 10));
233
+ await act(async () => {
234
+ await testSetup.renderOnce();
235
+ });
236
+ // Should not crash; frame should be valid
237
+ const frame = testSetup.captureCharFrame();
238
+ expect(frame).toContain("README.md");
239
+ });
240
+ it("toggles folder collapse with l/h when sidebar is focused", async () => {
241
+ const parsedFiles = [
242
+ createParsedFile("src/file-01.ts", 0),
243
+ createParsedFile("src/file-02.ts", 1),
244
+ ];
245
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
246
+ width: 120,
247
+ height: 12,
248
+ });
249
+ await act(async () => {
250
+ await testSetup.renderOnce();
251
+ });
252
+ const initialFrame = testSetup.captureCharFrame();
253
+ expect(initialFrame).toContain("src");
254
+ expect(initialFrame).toContain("file-01.ts");
255
+ expect(initialFrame).toContain("file-02.ts");
256
+ // Focus is on file-01.ts (row 1). Press k to move to src folder (row 0).
257
+ testSetup.mockInput.pressKey("k");
258
+ await new Promise((r) => setTimeout(r, 50));
259
+ await act(async () => {
260
+ await testSetup.renderOnce();
261
+ });
262
+ // Press l to collapse the folder
263
+ testSetup.mockInput.pressKey("l");
264
+ await new Promise((r) => setTimeout(r, 50));
265
+ await act(async () => {
266
+ await testSetup.renderOnce();
267
+ });
268
+ const afterCollapse = testSetup.captureCharFrame();
269
+ // Sidebar shows closed folder icon, no TypeScript file icons
270
+ expect(afterCollapse).toContain("󰉋");
271
+ expect(afterCollapse).not.toContain("󰛦");
272
+ // Press h to expand the folder
273
+ testSetup.mockInput.pressKey("h");
274
+ await new Promise((r) => setTimeout(r, 50));
275
+ await act(async () => {
276
+ await testSetup.renderOnce();
277
+ });
278
+ const afterExpand = testSetup.captureCharFrame();
279
+ // Sidebar shows open folder icon and TypeScript file icons again
280
+ expect(afterExpand).toContain("󰝰");
281
+ expect(afterExpand).toContain("󰛦");
282
+ });
283
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bun
2
+ import "./patch-terminal-dimensions.js";
3
+ import * as React from "react";
4
+ import { type ParsedFile } from "./diff-utils.js";
5
+ export interface AppProps {
6
+ parsedFiles: ParsedFile[];
7
+ }
8
+ export declare function App({ parsedFiles }: AppProps): React.ReactNode;
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";AAOA,OAAO,gCAAgC,CAAC;AAoBxC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgB/B,OAAO,EAoBN,KAAK,UAAU,EACf,MAAM,iBAAiB,CAAC;AAuIzB,MAAM,WAAW,QAAQ;IACxB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC1B;AAKD,wBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,SAAS,CAuzB9D"}