@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.
- package/dist/ansi-html.d.ts +42 -0
- package/dist/ansi-html.d.ts.map +1 -0
- package/dist/ansi-html.js +327 -0
- package/dist/ansi-output.d.ts +22 -0
- package/dist/ansi-output.d.ts.map +1 -0
- package/dist/ansi-output.js +154 -0
- package/dist/balance-delimiters.d.ts +25 -0
- package/dist/balance-delimiters.d.ts.map +1 -0
- package/dist/balance-delimiters.js +539 -0
- package/dist/balance-delimiters.test.d.ts +2 -0
- package/dist/balance-delimiters.test.d.ts.map +1 -0
- package/dist/balance-delimiters.test.js +1029 -0
- package/dist/cli-copy-notification.test.d.ts +2 -0
- package/dist/cli-copy-notification.test.d.ts.map +1 -0
- package/dist/cli-copy-notification.test.js +80 -0
- package/dist/cli-scroll.test.d.ts +2 -0
- package/dist/cli-scroll.test.d.ts.map +1 -0
- package/dist/cli-scroll.test.js +283 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +976 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +128 -0
- package/dist/components/diff-view.d.ts +32 -0
- package/dist/components/diff-view.d.ts.map +1 -0
- package/dist/components/diff-view.js +123 -0
- package/dist/components/diff-view.test.d.ts +5 -0
- package/dist/components/diff-view.test.d.ts.map +1 -0
- package/dist/components/diff-view.test.js +312 -0
- package/dist/components/directory-tree-view.d.ts +33 -0
- package/dist/components/directory-tree-view.d.ts.map +1 -0
- package/dist/components/directory-tree-view.js +262 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +5 -0
- package/dist/components/toast.d.ts +21 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toast.js +47 -0
- package/dist/diff-cursor-utils.d.ts +20 -0
- package/dist/diff-cursor-utils.d.ts.map +1 -0
- package/dist/diff-cursor-utils.js +105 -0
- package/dist/diff-cursor-utils.test.d.ts +2 -0
- package/dist/diff-cursor-utils.test.d.ts.map +1 -0
- package/dist/diff-cursor-utils.test.js +40 -0
- package/dist/diff-surface-copy.d.ts +23 -0
- package/dist/diff-surface-copy.d.ts.map +1 -0
- package/dist/diff-surface-copy.js +64 -0
- package/dist/diff-surface-copy.test.d.ts +5 -0
- package/dist/diff-surface-copy.test.d.ts.map +1 -0
- package/dist/diff-surface-copy.test.js +142 -0
- package/dist/diff-utils.d.ts +196 -0
- package/dist/diff-utils.d.ts.map +1 -0
- package/dist/diff-utils.js +682 -0
- package/dist/diff-utils.test.d.ts +2 -0
- package/dist/diff-utils.test.d.ts.map +1 -0
- package/dist/diff-utils.test.js +727 -0
- package/dist/directory-tree.d.ts +72 -0
- package/dist/directory-tree.d.ts.map +1 -0
- package/dist/directory-tree.js +161 -0
- package/dist/directory-tree.test.d.ts +2 -0
- package/dist/directory-tree.test.d.ts.map +1 -0
- package/dist/directory-tree.test.js +383 -0
- package/dist/dropdown.d.ts +26 -0
- package/dist/dropdown.d.ts.map +1 -0
- package/dist/dropdown.js +172 -0
- package/dist/dropdown.test.d.ts +2 -0
- package/dist/dropdown.test.d.ts.map +1 -0
- package/dist/dropdown.test.js +106 -0
- package/dist/filter-submodule.e2e.test.d.ts +2 -0
- package/dist/filter-submodule.e2e.test.d.ts.map +1 -0
- package/dist/filter-submodule.e2e.test.js +109 -0
- package/dist/hooks/use-copy-selection.d.ts +29 -0
- package/dist/hooks/use-copy-selection.d.ts.map +1 -0
- package/dist/hooks/use-copy-selection.js +46 -0
- package/dist/kv-codec.d.ts +16 -0
- package/dist/kv-codec.d.ts.map +1 -0
- package/dist/kv-codec.js +36 -0
- package/dist/license.d.ts +14 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +63 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +78 -0
- package/dist/monochrome.d.ts +34 -0
- package/dist/monochrome.d.ts.map +1 -0
- package/dist/monochrome.js +613 -0
- package/dist/monotone.d.ts +22 -0
- package/dist/monotone.d.ts.map +1 -0
- package/dist/monotone.js +185 -0
- package/dist/parsers-config.d.ts +19 -0
- package/dist/parsers-config.d.ts.map +1 -0
- package/dist/parsers-config.js +271 -0
- package/dist/patch-terminal-dimensions.d.ts +2 -0
- package/dist/patch-terminal-dimensions.d.ts.map +1 -0
- package/dist/patch-terminal-dimensions.js +45 -0
- package/dist/stdin-pager.test.d.ts +2 -0
- package/dist/stdin-pager.test.d.ts.map +1 -0
- package/dist/stdin-pager.test.js +497 -0
- package/dist/store.d.ts +16 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +48 -0
- package/dist/themes/github.json +247 -0
- package/dist/themes.d.ts +59 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +248 -0
- package/dist/tree-icons.d.ts +4 -0
- package/dist/tree-icons.d.ts.map +1 -0
- package/dist/tree-icons.js +18 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +13 -0
- package/dist/web-utils.d.ts +56 -0
- package/dist/web-utils.d.ts.map +1 -0
- package/dist/web-utils.js +363 -0
- package/package.json +37 -0
- package/public/jetbrains-mono-nerd.ttf +0 -0
- package/public/jetbrains-mono-nerd.woff2 +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CliRenderer } from "@opentuah/core";
|
|
2
|
+
/**
|
|
3
|
+
* Copy text to the system clipboard using native commands.
|
|
4
|
+
* Falls back to the renderer's OSC52 escape sequence for terminal clipboard.
|
|
5
|
+
*/
|
|
6
|
+
export declare function copyToClipboard(text: string, copyOsc52: (value: string) => boolean): Promise<void>;
|
|
7
|
+
/** Convenience wrapper that pulls the OSC52 helper from a CliRenderer. */
|
|
8
|
+
export declare function copyToClipboardWithRenderer(text: string, renderer: CliRenderer): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Synchronously copy text to the system clipboard using native commands.
|
|
11
|
+
* Falls back to the renderer's OSC52 escape sequence.
|
|
12
|
+
*/
|
|
13
|
+
export declare function copyToClipboardSync(text: string, copyOsc52: (value: string) => boolean): void;
|
|
14
|
+
/** Convenience wrapper that pulls the OSC52 helper from a CliRenderer. */
|
|
15
|
+
export declare function copyToClipboardWithRendererSync(text: string, renderer: CliRenderer): void;
|
|
16
|
+
//# sourceMappingURL=clipboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAiBjD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GACpC,OAAO,CAAC,IAAI,CAAC,CA6Cf;AAED,0EAA0E;AAC1E,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GACpC,IAAI,CA6CN;AAWD,0EAA0E;AAC1E,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,IAAI,CAEzF"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Clipboard helpers used by mouse and keyboard copy actions.
|
|
2
|
+
import childProcess from "child_process";
|
|
3
|
+
async function spawnClipboard(cmd, args, text) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const proc = childProcess.spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
6
|
+
proc.on("error", reject);
|
|
7
|
+
proc.on("close", (code) => {
|
|
8
|
+
if (code === 0)
|
|
9
|
+
resolve();
|
|
10
|
+
else
|
|
11
|
+
reject(new Error(`${cmd} exited with code ${code}`));
|
|
12
|
+
});
|
|
13
|
+
proc.stdin?.write(text);
|
|
14
|
+
proc.stdin?.end();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Copy text to the system clipboard using native commands.
|
|
19
|
+
* Falls back to the renderer's OSC52 escape sequence for terminal clipboard.
|
|
20
|
+
*/
|
|
21
|
+
export async function copyToClipboard(text, copyOsc52) {
|
|
22
|
+
const platform = process.platform;
|
|
23
|
+
try {
|
|
24
|
+
if (platform === "darwin") {
|
|
25
|
+
await spawnClipboard("pbcopy", [], text);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (platform === "linux") {
|
|
29
|
+
const isWayland = !!process.env.WAYLAND_DISPLAY;
|
|
30
|
+
if (isWayland) {
|
|
31
|
+
try {
|
|
32
|
+
await spawnClipboard("wl-copy", [], text);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Fall through to X11 tools
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await spawnClipboard("xclip", ["-selection", "clipboard"], text);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Try xsel
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await spawnClipboard("xsel", ["--clipboard", "--input"], text);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Fall through to OSC52
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (platform === "win32") {
|
|
55
|
+
await spawnClipboard("clip.exe", [], text);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Native clipboard failed, fall through to OSC52
|
|
61
|
+
}
|
|
62
|
+
copyOsc52(text);
|
|
63
|
+
}
|
|
64
|
+
/** Convenience wrapper that pulls the OSC52 helper from a CliRenderer. */
|
|
65
|
+
export function copyToClipboardWithRenderer(text, renderer) {
|
|
66
|
+
return copyToClipboard(text, (value) => renderer.copyToClipboardOSC52(value));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Synchronously copy text to the system clipboard using native commands.
|
|
70
|
+
* Falls back to the renderer's OSC52 escape sequence.
|
|
71
|
+
*/
|
|
72
|
+
export function copyToClipboardSync(text, copyOsc52) {
|
|
73
|
+
const platform = process.platform;
|
|
74
|
+
try {
|
|
75
|
+
if (platform === "darwin") {
|
|
76
|
+
spawnClipboardSync("pbcopy", [], text);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (platform === "linux") {
|
|
80
|
+
const isWayland = !!process.env.WAYLAND_DISPLAY;
|
|
81
|
+
if (isWayland) {
|
|
82
|
+
try {
|
|
83
|
+
spawnClipboardSync("wl-copy", [], text);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Fall through to X11 tools
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
spawnClipboardSync("xclip", ["-selection", "clipboard"], text);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Try xsel
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
spawnClipboardSync("xsel", ["--clipboard", "--input"], text);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Fall through to OSC52
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (platform === "win32") {
|
|
106
|
+
spawnClipboardSync("clip.exe", [], text);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Native clipboard failed, fall through to OSC52
|
|
112
|
+
}
|
|
113
|
+
copyOsc52(text);
|
|
114
|
+
}
|
|
115
|
+
function spawnClipboardSync(cmd, args, text) {
|
|
116
|
+
const result = childProcess.spawnSync(cmd, args, {
|
|
117
|
+
input: text,
|
|
118
|
+
encoding: "utf-8",
|
|
119
|
+
});
|
|
120
|
+
if (result.error)
|
|
121
|
+
throw result.error;
|
|
122
|
+
if (result.status !== 0)
|
|
123
|
+
throw new Error(`${cmd} exited with code ${result.status}`);
|
|
124
|
+
}
|
|
125
|
+
/** Convenience wrapper that pulls the OSC52 helper from a CliRenderer. */
|
|
126
|
+
export function copyToClipboardWithRendererSync(text, renderer) {
|
|
127
|
+
copyToClipboardSync(text, (value) => renderer.copyToClipboardOSC52(value));
|
|
128
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { DiffRenderable } from "@opentuah/core";
|
|
3
|
+
export interface DiffViewRef {
|
|
4
|
+
getDiffRenderable(): DiffRenderable | null;
|
|
5
|
+
}
|
|
6
|
+
export interface DiffViewProps {
|
|
7
|
+
diff: string;
|
|
8
|
+
view: "split" | "unified";
|
|
9
|
+
filetype?: string;
|
|
10
|
+
themeName: string;
|
|
11
|
+
/** Wrap mode for long lines (default: "word") */
|
|
12
|
+
wrapMode?: "word" | "char" | "none";
|
|
13
|
+
/** Enable italics in syntax highlighting (default: true) */
|
|
14
|
+
italicsEnabled?: boolean;
|
|
15
|
+
/** Use the terminal's default background instead of theme panel backgrounds */
|
|
16
|
+
transparentBackground?: boolean;
|
|
17
|
+
/** Whether the diff pane is focused and should show the cursor line */
|
|
18
|
+
focused?: boolean;
|
|
19
|
+
/** 0-based logical line index of the cursor */
|
|
20
|
+
cursorLine?: number;
|
|
21
|
+
/** Inclusive start/end of the current selection, or null */
|
|
22
|
+
selection?: {
|
|
23
|
+
start: number;
|
|
24
|
+
end: number;
|
|
25
|
+
} | null;
|
|
26
|
+
/** Background color for the cursor line (defaults to the sidebar active-file color) */
|
|
27
|
+
cursorColor?: string;
|
|
28
|
+
/** Background color for selected lines (defaults to selectionBg) */
|
|
29
|
+
selectionColor?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare const DiffView: React.ForwardRefExoticComponent<DiffViewProps & React.RefAttributes<DiffViewRef>>;
|
|
32
|
+
//# sourceMappingURL=diff-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-view.d.ts","sourceRoot":"","sources":["../../src/components/diff-view.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,cAAc,EAAqB,MAAM,gBAAgB,CAAA;AAIlE,MAAM,WAAW,WAAW;IAC1B,iBAAiB,IAAI,cAAc,GAAG,IAAI,CAAA;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,SAAS,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACnC,4DAA4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACjD,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAwDD,eAAO,MAAM,QAAQ,mFA4InB,CAAA"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@opentuah/react/jsx-runtime";
|
|
2
|
+
// Shared DiffView component for rendering git diffs with syntax highlighting.
|
|
3
|
+
// Wraps opentui's <diff> element with theme-aware colors and syntax styles.
|
|
4
|
+
// Supports split and unified view modes with line numbers.
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { DiffRenderable, RGBA, SyntaxStyle } from "@opentuah/core";
|
|
7
|
+
import { getSyntaxTheme, getResolvedTheme, rgbaToHex } from "../themes.js";
|
|
8
|
+
import { balanceDelimiters } from "../balance-delimiters.js";
|
|
9
|
+
function getLuminance(color) {
|
|
10
|
+
return color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722;
|
|
11
|
+
}
|
|
12
|
+
function getColorDistance(a, b) {
|
|
13
|
+
const dr = a.r - b.r;
|
|
14
|
+
const dg = a.g - b.g;
|
|
15
|
+
const db = a.b - b.b;
|
|
16
|
+
return Math.sqrt(dr * dr + dg * dg + db * db);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compute a visible word-highlight color from a line background.
|
|
20
|
+
* Uses stronger contrast than opentui's default brighten(1.1), which is too subtle
|
|
21
|
+
* on very dark themes (especially in HTML screenshots/web output).
|
|
22
|
+
*/
|
|
23
|
+
function getWordHighlightBg(base) {
|
|
24
|
+
const baseLuminance = getLuminance(base);
|
|
25
|
+
// Light backgrounds: darken slightly while preserving hue.
|
|
26
|
+
// This avoids chalky white patches that can look noisy.
|
|
27
|
+
if (baseLuminance >= 0.52) {
|
|
28
|
+
return rgbaToHex(base.brighten(0.9));
|
|
29
|
+
}
|
|
30
|
+
// Dark backgrounds: use stronger hue-preserving brightening than opentui default (1.1),
|
|
31
|
+
// which is often imperceptible on near-black diff backgrounds.
|
|
32
|
+
let candidate = base.brighten(2.4);
|
|
33
|
+
let luminanceDelta = Math.abs(getLuminance(candidate) - baseLuminance);
|
|
34
|
+
// Keep a minimum luminance delta close to github-light perceptibility.
|
|
35
|
+
if (luminanceDelta < 0.09) {
|
|
36
|
+
candidate = base.brighten(3.0);
|
|
37
|
+
luminanceDelta = Math.abs(getLuminance(candidate) - baseLuminance);
|
|
38
|
+
}
|
|
39
|
+
if (luminanceDelta < 0.09) {
|
|
40
|
+
candidate = base.brighten(3.6);
|
|
41
|
+
}
|
|
42
|
+
// Pure-black (or near-black) bases stay unchanged with multiplicative brighten.
|
|
43
|
+
// Add a tiny additive lift so inline highlights remain visible on those themes.
|
|
44
|
+
if (getColorDistance(candidate, base) < 0.03) {
|
|
45
|
+
candidate = RGBA.fromValues(base.r + (1 - base.r) * 0.12, base.g + (1 - base.g) * 0.12, base.b + (1 - base.b) * 0.12, base.a);
|
|
46
|
+
}
|
|
47
|
+
return rgbaToHex(candidate);
|
|
48
|
+
}
|
|
49
|
+
export const DiffView = React.forwardRef(function DiffView({ diff, view, filetype, themeName, wrapMode = "word", italicsEnabled = true, transparentBackground = false, focused = false, cursorLine = 0, selection = null, cursorColor, selectionColor, }, ref) {
|
|
50
|
+
const diffRef = React.useRef(null);
|
|
51
|
+
React.useImperativeHandle(ref, () => ({
|
|
52
|
+
getDiffRenderable: () => diffRef.current,
|
|
53
|
+
}));
|
|
54
|
+
// Balance paired delimiters (backticks, triple quotes, etc.) before
|
|
55
|
+
// passing to <diff> so tree-sitter doesn't misparse hunks that start
|
|
56
|
+
// inside a multi-line string
|
|
57
|
+
const balancedDiff = React.useMemo(() => balanceDelimiters(diff, filetype), [diff, filetype]);
|
|
58
|
+
// Memoize theme lookups to ensure stable references
|
|
59
|
+
const resolvedTheme = React.useMemo(() => getResolvedTheme(themeName), [themeName]);
|
|
60
|
+
const syntaxStyle = React.useMemo(() => SyntaxStyle.fromStyles(getSyntaxTheme(themeName, "dark", italicsEnabled)), [themeName, italicsEnabled]);
|
|
61
|
+
// Convert RGBA to hex for diff component props
|
|
62
|
+
const transparentBg = React.useMemo(() => RGBA.fromInts(0, 0, 0, 0), []);
|
|
63
|
+
const colors = React.useMemo(() => ({
|
|
64
|
+
text: rgbaToHex(resolvedTheme.text),
|
|
65
|
+
bgPanel: transparentBackground ? transparentBg : rgbaToHex(resolvedTheme.backgroundPanel),
|
|
66
|
+
diffAddedBg: rgbaToHex(resolvedTheme.diffAddedBg),
|
|
67
|
+
diffRemovedBg: rgbaToHex(resolvedTheme.diffRemovedBg),
|
|
68
|
+
diffLineNumber: rgbaToHex(resolvedTheme.diffLineNumber),
|
|
69
|
+
diffAddedLineNumberBg: rgbaToHex(resolvedTheme.diffAddedLineNumberBg),
|
|
70
|
+
diffRemovedLineNumberBg: rgbaToHex(resolvedTheme.diffRemovedLineNumberBg),
|
|
71
|
+
}), [resolvedTheme, transparentBackground, transparentBg]);
|
|
72
|
+
const wordHighlights = React.useMemo(() => ({
|
|
73
|
+
addedWordBg: getWordHighlightBg(resolvedTheme.diffAddedBg),
|
|
74
|
+
removedWordBg: getWordHighlightBg(resolvedTheme.diffRemovedBg),
|
|
75
|
+
}), [resolvedTheme]);
|
|
76
|
+
const activeCursorColor = React.useMemo(() => {
|
|
77
|
+
if (cursorColor)
|
|
78
|
+
return cursorColor;
|
|
79
|
+
// Match the sidebar's active-file background so the focused cursor line
|
|
80
|
+
// feels visually consistent with the file navigator.
|
|
81
|
+
return rgbaToHex(resolvedTheme.primary.brighten(0.3));
|
|
82
|
+
}, [cursorColor, resolvedTheme.primary]);
|
|
83
|
+
const activeSelectionColor = React.useMemo(() => {
|
|
84
|
+
return selectionColor ?? "#264F78";
|
|
85
|
+
}, [selectionColor]);
|
|
86
|
+
// Track previously-applied highlights so we can clear only our own overrides.
|
|
87
|
+
const prevCursorRef = React.useRef(null);
|
|
88
|
+
const prevSelectionRef = React.useRef(null);
|
|
89
|
+
// Apply cursor line and selection highlights to the underlying DiffRenderable.
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
const diffRenderable = diffRef.current;
|
|
92
|
+
if (!diffRenderable)
|
|
93
|
+
return;
|
|
94
|
+
// Clear previous cursor override.
|
|
95
|
+
if (prevCursorRef.current) {
|
|
96
|
+
diffRenderable.clearLineColor(prevCursorRef.current.line);
|
|
97
|
+
}
|
|
98
|
+
// Clear previous selection overrides.
|
|
99
|
+
if (prevSelectionRef.current) {
|
|
100
|
+
diffRenderable.clearHighlightLines(prevSelectionRef.current.start, prevSelectionRef.current.end);
|
|
101
|
+
}
|
|
102
|
+
prevCursorRef.current = null;
|
|
103
|
+
prevSelectionRef.current = null;
|
|
104
|
+
if (!focused)
|
|
105
|
+
return;
|
|
106
|
+
if (selection) {
|
|
107
|
+
const start = Math.min(selection.start, selection.end);
|
|
108
|
+
const end = Math.max(selection.start, selection.end);
|
|
109
|
+
if (end >= start) {
|
|
110
|
+
diffRenderable.highlightLines(start, end, activeSelectionColor);
|
|
111
|
+
prevSelectionRef.current = { start, end, color: activeSelectionColor };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
diffRenderable.setLineColor(cursorLine, activeCursorColor);
|
|
115
|
+
prevCursorRef.current = { line: cursorLine, color: activeCursorColor };
|
|
116
|
+
}, [focused, cursorLine, selection, activeCursorColor, activeSelectionColor]);
|
|
117
|
+
return (_jsx("box", { style: { backgroundColor: colors.bgPanel }, children: _jsx("diff", { ref: diffRef, diff: balancedDiff, view: view, fg: colors.text, treeSitterClient: undefined, filetype: filetype, syntaxStyle: syntaxStyle, showLineNumbers: true, wrapMode: wrapMode,
|
|
118
|
+
// `addedBg`/`removedBg` are used by opentui as the base colors for word-level highlights.
|
|
119
|
+
// We set them to match the content backgrounds so light themes don't inherit dark defaults.
|
|
120
|
+
addedBg: colors.diffAddedBg, removedBg: colors.diffRemovedBg, contextBg: colors.bgPanel,
|
|
121
|
+
// Use explicit word highlight colors to avoid near-invisible defaults on dark themes.
|
|
122
|
+
addedWordBg: wordHighlights.addedWordBg, removedWordBg: wordHighlights.removedWordBg, addedContentBg: colors.diffAddedBg, removedContentBg: colors.diffRemovedBg, contextContentBg: colors.bgPanel, lineNumberFg: colors.diffLineNumber, lineNumberBg: colors.bgPanel, addedLineNumberBg: colors.diffAddedLineNumberBg, removedLineNumberBg: colors.diffRemovedLineNumberBg, selectionBg: "#264F78", selectionFg: "#FFFFFF" }) }, themeName));
|
|
123
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-view.test.d.ts","sourceRoot":"","sources":["../../src/components/diff-view.test.tsx"],"names":[],"mappings":"AASA,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,wBAAwB,EAAE,OAAO,GAAG,SAAS,CAAA;CAClD"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@opentuah/react/jsx-runtime";
|
|
2
|
+
// Tests for DiffView theme reactivity when switching themes at runtime.
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
|
+
import { testRender } from "@opentuah/react/test-utils";
|
|
6
|
+
import { getDataPaths } from "@opentuah/core";
|
|
7
|
+
import { DiffView } from "./diff-view.js";
|
|
8
|
+
import { useAppStore } from "../store.js";
|
|
9
|
+
// Suppress EventTarget memory leak warning from opentui DataPathsManager —
|
|
10
|
+
// each DiffView registers a paths:changed listener during tree-sitter init
|
|
11
|
+
getDataPaths().setMaxListeners(50);
|
|
12
|
+
const sampleDiff = `diff --git a/a.txt b/a.txt
|
|
13
|
+
index 1111111..2222222 100644
|
|
14
|
+
--- a/a.txt
|
|
15
|
+
+++ b/a.txt
|
|
16
|
+
@@ -1,2 +1,2 @@
|
|
17
|
+
-old
|
|
18
|
+
+new
|
|
19
|
+
keep
|
|
20
|
+
`;
|
|
21
|
+
const wordHighlightDiff = `diff --git a/a.ts b/a.ts
|
|
22
|
+
index 1111111..2222222 100644
|
|
23
|
+
--- a/a.ts
|
|
24
|
+
+++ b/a.ts
|
|
25
|
+
@@ -1 +1 @@
|
|
26
|
+
-const value = oldName + 1
|
|
27
|
+
+const value = newName + 1
|
|
28
|
+
`;
|
|
29
|
+
const databaseRegressionDiff = `--- cli/src/database.ts
|
|
30
|
+
+++ cli/src/database.ts
|
|
31
|
+
@@ -9,17 +9,18 @@
|
|
32
|
+
import dedent from "string-dedent";
|
|
33
|
+
import { browserLogin } from "./tinybird-browser-login.ts";
|
|
34
|
+
import { loadTinybirdResources } from "./tinybird-resources.ts";
|
|
35
|
+
import { deployTinybirdResources, getDeploymentManagedReadToken, TinybirdClient } from "./tinybird.ts";
|
|
36
|
+
import { requireAuth } from "./config.ts";
|
|
37
|
+
import { getApiClient } from "./api-client.ts";
|
|
38
|
+
-import { ensureDefaultOrg } from "./projects.ts";
|
|
39
|
+
+import { resolveCurrentOrg } from "./orgs.ts";
|
|
40
|
+
|
|
41
|
+
export interface DatabaseCreateOptions {
|
|
42
|
+
token?: string;
|
|
43
|
+
baseUrl?: string;
|
|
44
|
+
+ force?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getTinybirdEnvAuth() {
|
|
48
|
+
const token = process.env.TINYBIRD_TOKEN || process.env.TB_TOKEN
|
|
49
|
+
const baseUrl = process.env.TINYBIRD_BASE_URL || process.env.TINYBIRD_HOST || process.env.TB_HOST
|
|
50
|
+
if (!token) return null
|
|
51
|
+
@@ -47,12 +48,13 @@
|
|
52
|
+
|
|
53
|
+
For non-interactive Tinybird auth, pass --token and --base-url directly.
|
|
54
|
+
\`,
|
|
55
|
+
)
|
|
56
|
+
.option("-t, --token [token]", "Tinybird workspace admin token (skips browser login)")
|
|
57
|
+
.option("-u, --base-url [url]", "Tinybird API base URL (e.g. https://api.us-east.aws.tinybird.co)")
|
|
58
|
+
+ .option("-f, --force", "Overwrite existing database config without confirmation")
|
|
59
|
+
.example("# Interactive setup (opens browser)")
|
|
60
|
+
.example("strada database create")
|
|
61
|
+
.example("# Non-interactive with existing token")
|
|
62
|
+
.example("strada database create --token p.eyXXX --base-url https://api.tinybird.co")
|
|
63
|
+
.action((options, context) => databaseCreateAction(options, context));
|
|
64
|
+
|
|
65
|
+
@@ -84,19 +86,46 @@
|
|
66
|
+
clack.log.error((e as Error).message);
|
|
67
|
+
return proc.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create a default personal org on first use so \`strada login\` -> \`strada database create\`
|
|
71
|
+
// works end to end without a manual org bootstrap step.
|
|
72
|
+
- const org = await ensureDefaultOrg().catch((error) => error as Error);
|
|
73
|
+
+ const org = await resolveCurrentOrg().catch((error) => error as Error);
|
|
74
|
+
if (org instanceof Error) {
|
|
75
|
+
clack.log.error(org.message);
|
|
76
|
+
return proc.exit(1);
|
|
77
|
+
}
|
|
78
|
+
clack.log.info(\`Using organization: \${cyan(org.name)}\`);
|
|
79
|
+
|
|
80
|
+
+ // Check if this org already has a configured database. If so, require
|
|
81
|
+
+ // explicit confirmation (interactive) or --force (non-interactive) to
|
|
82
|
+
+ // prevent accidental overwrites of Tinybird tokens.
|
|
83
|
+
+ if (!options.force) {
|
|
84
|
+
+ const { safeFetch: checkFetch } = getApiClient();
|
|
85
|
+
+ const existingDb = await checkFetch("/api/v0/orgs/:orgId/database", {
|
|
86
|
+
+ params: { orgId: org.id },
|
|
87
|
+
+ });
|
|
88
|
+
+ if (!(existingDb instanceof Error) && (existingDb.hasAdminToken || existingDb.hasReadToken)) {
|
|
89
|
+
+ const endpoint = existingDb.tinybirdEndpoint || existingDb.clickhouseUrl || "unknown";
|
|
90
|
+
+ if (!process.stdin.isTTY) {
|
|
91
|
+
+ clack.log.error(
|
|
92
|
+
+ \`This org already has a configured \${existingDb.backend} database (\${endpoint}).\\n\` +
|
|
93
|
+
+ \` Pass --force to overwrite it.\`,
|
|
94
|
+
+ );
|
|
95
|
+
+ return proc.exit(1);
|
|
96
|
+
+ }
|
|
97
|
+
+ const overwrite = await clack.confirm({
|
|
98
|
+
+ message: \`This org already has a configured \${existingDb.backend} database (\${endpoint}). Overwrite it?\`,
|
|
99
|
+
+ });
|
|
100
|
+
+ if (clack.isCancel(overwrite) || !overwrite) {
|
|
101
|
+
+ clack.outro("Cancelled. Existing database config is unchanged.");
|
|
102
|
+
+ return proc.exit(0);
|
|
103
|
+
+ }
|
|
104
|
+
+ }
|
|
105
|
+
+ }
|
|
106
|
+
+
|
|
107
|
+
// Authenticate with Tinybird
|
|
108
|
+
const auth = await (async () => {
|
|
109
|
+
if (options.token && options.baseUrl) {
|
|
110
|
+
clack.log.info(\`Using provided token for \${options.baseUrl}\`);
|
|
111
|
+
return { token: options.token, baseUrl: options.baseUrl };
|
|
112
|
+
}
|
|
113
|
+
@@ -235,13 +264,13 @@
|
|
114
|
+
requireAuth();
|
|
115
|
+
} catch (e) {
|
|
116
|
+
clack.log.error((e as Error).message);
|
|
117
|
+
return proc.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
- const org = await ensureDefaultOrg().catch((error) => error as Error);
|
|
121
|
+
+ const org = await resolveCurrentOrg().catch((error) => error as Error);
|
|
122
|
+
if (org instanceof Error) {
|
|
123
|
+
clack.log.error(org.message);
|
|
124
|
+
return proc.exit(1);
|
|
125
|
+
}
|
|
126
|
+
clack.log.info(\`Using organization: \${cyan(org.name)}\`);
|
|
127
|
+
`;
|
|
128
|
+
function ThemeToggleHarness() {
|
|
129
|
+
const themeName = useAppStore((s) => s.themeName);
|
|
130
|
+
return (_jsx(DiffView, { diff: sampleDiff, view: "unified", filetype: "txt", themeName: themeName }));
|
|
131
|
+
}
|
|
132
|
+
function extractDiffBackgroundSample(frame) {
|
|
133
|
+
return {
|
|
134
|
+
removedLineNumberBg: Array.from(frame.lines[0].spans[0].bg.buffer),
|
|
135
|
+
removedContentBg: Array.from(frame.lines[0].spans[4].bg.buffer),
|
|
136
|
+
contextBg: Array.from(frame.lines[2].spans[0].bg.buffer),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function getLineWithToken(frame, token) {
|
|
140
|
+
return frame.lines.find((line) => line.spans.map((span) => span.text).join("").includes(token));
|
|
141
|
+
}
|
|
142
|
+
function getWordHighlightDistance(frame) {
|
|
143
|
+
const removedLine = getLineWithToken(frame, "old");
|
|
144
|
+
const addedLine = getLineWithToken(frame, "new");
|
|
145
|
+
if (!removedLine || !addedLine) {
|
|
146
|
+
throw new Error("Expected both added and removed lines in rendered frame");
|
|
147
|
+
}
|
|
148
|
+
const removedWord = removedLine.spans.find((span) => span.text === "old");
|
|
149
|
+
const removedBase = removedLine.spans.find((span) => span.text.includes("Name"));
|
|
150
|
+
const addedWord = addedLine.spans.find((span) => span.text === "new");
|
|
151
|
+
const addedBase = addedLine.spans.find((span) => span.text.includes("Name"));
|
|
152
|
+
if (!removedWord || !removedBase || !addedWord || !addedBase) {
|
|
153
|
+
throw new Error("Expected split word/background spans for inline highlights");
|
|
154
|
+
}
|
|
155
|
+
const distance = (a, b) => {
|
|
156
|
+
const dr = a[0] - b[0];
|
|
157
|
+
const dg = a[1] - b[1];
|
|
158
|
+
const db = a[2] - b[2];
|
|
159
|
+
return Math.sqrt(dr * dr + dg * dg + db * db);
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
removed: distance(removedWord.bg.buffer, removedBase.bg.buffer),
|
|
163
|
+
added: distance(addedWord.bg.buffer, addedBase.bg.buffer),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// Suppress React act() warnings for opentui component tests.
|
|
167
|
+
// opentui's internal rendering triggers state updates outside act() boundaries,
|
|
168
|
+
// which is expected behavior for TUI component testing.
|
|
169
|
+
// testRender sets IS_REACT_ACT_ENVIRONMENT=true, so we must disable it after.
|
|
170
|
+
async function setupTest(jsx, opts) {
|
|
171
|
+
const setup = await testRender(jsx, opts);
|
|
172
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = false;
|
|
173
|
+
return setup;
|
|
174
|
+
}
|
|
175
|
+
async function renderSeveralTimes(testSetup, times) {
|
|
176
|
+
for (let index = 0; index < times; index++) {
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
178
|
+
await testSetup.renderOnce();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
describe("DiffView", () => {
|
|
182
|
+
let testSetup;
|
|
183
|
+
afterEach(() => {
|
|
184
|
+
if (testSetup) {
|
|
185
|
+
testSetup.renderer.destroy();
|
|
186
|
+
}
|
|
187
|
+
useAppStore.setState({ themeName: "github" });
|
|
188
|
+
});
|
|
189
|
+
it("updates diff background colors after theme switch", async () => {
|
|
190
|
+
useAppStore.setState({ themeName: "github" });
|
|
191
|
+
testSetup = await setupTest(_jsx(ThemeToggleHarness, {}), {
|
|
192
|
+
width: 80,
|
|
193
|
+
height: 8,
|
|
194
|
+
});
|
|
195
|
+
await testSetup.renderOnce();
|
|
196
|
+
const before = extractDiffBackgroundSample(testSetup.captureSpans());
|
|
197
|
+
expect(before).toMatchInlineSnapshot(`
|
|
198
|
+
{
|
|
199
|
+
"contextBg": [
|
|
200
|
+
0,
|
|
201
|
+
0,
|
|
202
|
+
0,
|
|
203
|
+
1,
|
|
204
|
+
],
|
|
205
|
+
"removedContentBg": [
|
|
206
|
+
0.21176470816135406,
|
|
207
|
+
0.11764705926179886,
|
|
208
|
+
0.11764705926179886,
|
|
209
|
+
1,
|
|
210
|
+
],
|
|
211
|
+
"removedLineNumberBg": [
|
|
212
|
+
0.10980392247438431,
|
|
213
|
+
0.05098039284348488,
|
|
214
|
+
0.054901961237192154,
|
|
215
|
+
1,
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
`);
|
|
219
|
+
useAppStore.setState({ themeName: "tokyonight" });
|
|
220
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
221
|
+
await testSetup.renderOnce();
|
|
222
|
+
const after = extractDiffBackgroundSample(testSetup.captureSpans());
|
|
223
|
+
expect(after).toMatchInlineSnapshot(`
|
|
224
|
+
{
|
|
225
|
+
"contextBg": [
|
|
226
|
+
0.11764705926179886,
|
|
227
|
+
0.125490203499794,
|
|
228
|
+
0.1882352977991104,
|
|
229
|
+
1,
|
|
230
|
+
],
|
|
231
|
+
"removedContentBg": [
|
|
232
|
+
0.5176470875740051,
|
|
233
|
+
0.32156863808631897,
|
|
234
|
+
0.4156862795352936,
|
|
235
|
+
1,
|
|
236
|
+
],
|
|
237
|
+
"removedLineNumberBg": [
|
|
238
|
+
0.1764705926179886,
|
|
239
|
+
0.12156862765550613,
|
|
240
|
+
0.14901961386203766,
|
|
241
|
+
1,
|
|
242
|
+
],
|
|
243
|
+
}
|
|
244
|
+
`);
|
|
245
|
+
expect(after).not.toEqual(before);
|
|
246
|
+
});
|
|
247
|
+
it("keeps word highlights visible on github dark theme", async () => {
|
|
248
|
+
testSetup = await setupTest(_jsx(DiffView, { diff: wordHighlightDiff, view: "unified", filetype: "ts", themeName: "github" }), {
|
|
249
|
+
width: 80,
|
|
250
|
+
height: 8,
|
|
251
|
+
});
|
|
252
|
+
await testSetup.renderOnce();
|
|
253
|
+
const highlights = getWordHighlightDistance(testSetup.captureSpans());
|
|
254
|
+
expect(highlights.removed).toBeGreaterThan(0.03);
|
|
255
|
+
expect(highlights.added).toBeGreaterThan(0.03);
|
|
256
|
+
});
|
|
257
|
+
it("keeps word highlights visible on near-black themes", async () => {
|
|
258
|
+
testSetup = await setupTest(_jsx(DiffView, { diff: wordHighlightDiff, view: "unified", filetype: "ts", themeName: "lucent-orng" }), {
|
|
259
|
+
width: 80,
|
|
260
|
+
height: 8,
|
|
261
|
+
});
|
|
262
|
+
await testSetup.renderOnce();
|
|
263
|
+
const highlights = getWordHighlightDistance(testSetup.captureSpans());
|
|
264
|
+
expect(highlights.removed).toBeGreaterThan(0.01);
|
|
265
|
+
expect(highlights.added).toBeGreaterThan(0.01);
|
|
266
|
+
});
|
|
267
|
+
it("preserves syntax highlighting for hunks that start inside a template literal", async () => {
|
|
268
|
+
testSetup = await setupTest(_jsx(DiffView, { diff: databaseRegressionDiff, view: "unified", filetype: "typescript", themeName: "github" }), {
|
|
269
|
+
width: 220,
|
|
270
|
+
height: 120,
|
|
271
|
+
});
|
|
272
|
+
await renderSeveralTimes(testSetup, 5);
|
|
273
|
+
const frame = testSetup.captureSpans();
|
|
274
|
+
const importLine = getLineWithToken(frame, 'import dedent from "string-dedent"');
|
|
275
|
+
const endpointLine = getLineWithToken(frame, 'const endpoint = existingDb.tinybirdEndpoint');
|
|
276
|
+
expect(importLine?.spans.map((span) => span.text).filter((text) => text.trim() !== "")).toEqual(expect.arrayContaining(["import", "dedent", "from", '"string-dedent"']));
|
|
277
|
+
expect(endpointLine?.spans.map((span) => span.text).filter((text) => text.trim() !== "")).toEqual(expect.arrayContaining(["const", "endpoint", "=", '"unknown"']));
|
|
278
|
+
});
|
|
279
|
+
it("highlights the cursor line when focused", async () => {
|
|
280
|
+
testSetup = await setupTest(_jsx(DiffView, { diff: sampleDiff, view: "unified", filetype: "txt", themeName: "github", focused: true, cursorLine: 2, cursorColor: "#123456" }), {
|
|
281
|
+
width: 80,
|
|
282
|
+
height: 8,
|
|
283
|
+
});
|
|
284
|
+
await testSetup.renderOnce();
|
|
285
|
+
const frame = testSetup.captureSpans();
|
|
286
|
+
// Logical line order for sampleDiff: 0 hunk-header, 1 removed "old", 2 added "new", 3 context "keep"
|
|
287
|
+
const addedLine = frame.lines[2];
|
|
288
|
+
expect(addedLine).toBeDefined();
|
|
289
|
+
const cursorBg = Array.from(addedLine.spans[0].bg.buffer);
|
|
290
|
+
expect(cursorBg.slice(0, 3)).toEqual([
|
|
291
|
+
expect.closeTo(18 / 255, 4),
|
|
292
|
+
expect.closeTo(52 / 255, 4),
|
|
293
|
+
expect.closeTo(86 / 255, 4),
|
|
294
|
+
]);
|
|
295
|
+
});
|
|
296
|
+
it("does not highlight a cursor line when not focused", async () => {
|
|
297
|
+
testSetup = await setupTest(_jsx(DiffView, { diff: sampleDiff, view: "unified", filetype: "txt", themeName: "github", focused: false, cursorLine: 2, cursorColor: "#123456" }), {
|
|
298
|
+
width: 80,
|
|
299
|
+
height: 8,
|
|
300
|
+
});
|
|
301
|
+
await testSetup.renderOnce();
|
|
302
|
+
const frame = testSetup.captureSpans();
|
|
303
|
+
const addedLine = frame.lines[2];
|
|
304
|
+
expect(addedLine).toBeDefined();
|
|
305
|
+
const cursorBg = Array.from(addedLine.spans[0].bg.buffer);
|
|
306
|
+
expect(cursorBg.slice(0, 3)).not.toEqual([
|
|
307
|
+
expect.closeTo(18 / 255, 4),
|
|
308
|
+
expect.closeTo(52 / 255, 4),
|
|
309
|
+
expect.closeTo(86 / 255, 4),
|
|
310
|
+
]);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type TreeFileInfo } from "../directory-tree.js";
|
|
3
|
+
export declare const DEFAULT_SIDEBAR_WIDTH = 60;
|
|
4
|
+
export interface DirectoryTreeViewRef {
|
|
5
|
+
focusNext(): void;
|
|
6
|
+
focusPrev(): void;
|
|
7
|
+
toggleCollapse(): void;
|
|
8
|
+
getActiveRowIndex(): number;
|
|
9
|
+
}
|
|
10
|
+
export interface DirectoryTreeViewProps {
|
|
11
|
+
/** Files to display in the tree */
|
|
12
|
+
files: TreeFileInfo[];
|
|
13
|
+
/** Callback when a file is clicked (receives fileIndex) */
|
|
14
|
+
onFileSelect?: (fileIndex: number) => void;
|
|
15
|
+
/** Callback when a folder is focused (receives folder displayPath) */
|
|
16
|
+
onFolderSelect?: (folderPath: string) => void;
|
|
17
|
+
/** Theme name for colors */
|
|
18
|
+
themeName: string;
|
|
19
|
+
/** Fixed render width for sidebar layout */
|
|
20
|
+
width?: number;
|
|
21
|
+
/** Index of the currently active file (for highlight) */
|
|
22
|
+
activeFileIndex?: number;
|
|
23
|
+
/** Path of the currently active folder (for highlight) */
|
|
24
|
+
activeFolderPath?: string;
|
|
25
|
+
/** Callback whenever the focused row index changes */
|
|
26
|
+
onFocusRowChange?: (rowIndex: number) => void;
|
|
27
|
+
/** Paths of folders that should start collapsed */
|
|
28
|
+
initialCollapsedPaths?: string[];
|
|
29
|
+
/** Use the terminal's default background instead of theme panel backgrounds */
|
|
30
|
+
transparentBackground?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const DirectoryTreeView: React.ForwardRefExoticComponent<DirectoryTreeViewProps & React.RefAttributes<DirectoryTreeViewRef>>;
|
|
33
|
+
//# sourceMappingURL=directory-tree-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory-tree-view.d.ts","sourceRoot":"","sources":["../../src/components/directory-tree-view.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAoD,KAAK,YAAY,EAAiB,MAAM,sBAAsB,CAAA;AAMzH,eAAO,MAAM,qBAAqB,KAAK,CAAA;AAEvC,MAAM,WAAW,oBAAoB;IACnC,SAAS,IAAI,IAAI,CAAA;IACjB,SAAS,IAAI,IAAI,CAAA;IACjB,cAAc,IAAI,IAAI,CAAA;IACtB,iBAAiB,IAAI,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,sEAAsE;IACtE,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,mDAAmD;IACnD,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAA;IAChC,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAyLD,eAAO,MAAM,iBAAiB,qGA6L7B,CAAA"}
|