@abelfubu/dv 0.1.0 → 1.0.1
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/README.md +111 -0
- package/dist/cli-copy-notification.test.js +7 -3
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/components/diff-view.d.ts +1 -1
- package/dist/components/diff-view.d.ts.map +1 -1
- package/dist/components/diff-view.js +69 -8
- package/dist/components/diff-view.test.d.ts.map +1 -1
- package/dist/components/diff-view.test.js +54 -0
- package/dist/components/toast.js +1 -1
- package/dist/themes/aura.json +69 -0
- package/dist/themes/ayu.json +80 -0
- package/dist/themes/catppuccin-frappe.json +233 -0
- package/dist/themes/catppuccin-macchiato.json +233 -0
- package/dist/themes/catppuccin.json +112 -0
- package/dist/themes/cobalt2.json +228 -0
- package/dist/themes/cursor.json +249 -0
- package/dist/themes/dracula.json +219 -0
- package/dist/themes/everforest.json +241 -0
- package/dist/themes/flexoki.json +237 -0
- package/dist/themes/github-light.json +56 -0
- package/dist/themes/github.json +244 -244
- package/dist/themes/gruvbox.json +95 -0
- package/dist/themes/kanagawa.json +77 -0
- package/dist/themes/lucent-orng.json +227 -0
- package/dist/themes/material.json +235 -0
- package/dist/themes/matrix.json +77 -0
- package/dist/themes/mercury.json +252 -0
- package/dist/themes/monokai.json +221 -0
- package/dist/themes/muted-slate.json +56 -0
- package/dist/themes/nightowl.json +221 -0
- package/dist/themes/nord.json +223 -0
- package/dist/themes/one-dark.json +84 -0
- package/dist/themes/opencode-light.json +62 -0
- package/dist/themes/opencode.json +245 -0
- package/dist/themes/orng.json +245 -0
- package/dist/themes/palenight.json +222 -0
- package/dist/themes/rosepine.json +234 -0
- package/dist/themes/solarized.json +223 -0
- package/dist/themes/synthwave84.json +226 -0
- package/dist/themes/tokyonight.json +243 -0
- package/dist/themes/vercel.json +255 -0
- package/dist/themes/vesper.json +218 -0
- package/dist/themes/zenburn.json +223 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +4 -4
- package/package.json +11 -3
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# `dv` — Diffview
|
|
2
|
+
|
|
3
|
+
A fast, keyboard-driven diff viewer for the terminal. Pipe any `git diff` into it and navigate changes with a TUI, copy selections, switch themes, and more.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun install -g @abelfubu/dv
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires [Bun](https://bun.sh/).
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Pipe git diff output
|
|
19
|
+
git diff | dv
|
|
20
|
+
|
|
21
|
+
# Diff specific refs
|
|
22
|
+
git diff main...feature | dv
|
|
23
|
+
|
|
24
|
+
# Show working tree changes (default when stdin is empty)
|
|
25
|
+
dv
|
|
26
|
+
|
|
27
|
+
# Scrollback mode for non-TTY output
|
|
28
|
+
git diff | dv --scrollback
|
|
29
|
+
|
|
30
|
+
# Transparent background
|
|
31
|
+
dv --transparent
|
|
32
|
+
|
|
33
|
+
# Watch for changes
|
|
34
|
+
dv --watch
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Interactive TUI** built with [`@opentuah/react`](https://github.com/opentuah/opentuah)
|
|
40
|
+
- **Unified and split view** modes (auto-switches based on terminal width)
|
|
41
|
+
- **Directory tree** sidebar for jumping between files
|
|
42
|
+
- **Keyboard selection** and **clipboard copy** of diff text
|
|
43
|
+
- **Syntax highlighting** via Tree-sitter parsers
|
|
44
|
+
- **Themes** — switch at runtime
|
|
45
|
+
- **Transparent background** mode
|
|
46
|
+
- **Scrollback output** when stdout isn't a TTY
|
|
47
|
+
- **Watch mode** for live diff updates
|
|
48
|
+
- **Submodule support** — dirty submodules shown inline
|
|
49
|
+
- **Untracked files** displayed via synthetic diffs
|
|
50
|
+
|
|
51
|
+
## Keybindings
|
|
52
|
+
|
|
53
|
+
| Key | Action |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `↑` / `↓` or `k` / `j` | Move cursor |
|
|
56
|
+
| `←` / `→` or `h` / `l` | Switch focus (tree / diff) |
|
|
57
|
+
| `Enter` | Jump to file under cursor |
|
|
58
|
+
| `Tab` | Toggle focus between tree and diff |
|
|
59
|
+
| `u` | Toggle unified / split view |
|
|
60
|
+
| `v` | Set selection anchor |
|
|
61
|
+
| `y` | Copy selected diff content |
|
|
62
|
+
| `t` | Cycle themes |
|
|
63
|
+
| `T` | Toggle transparent background |
|
|
64
|
+
| `c` | Change context lines |
|
|
65
|
+
| `r` | Refresh diff |
|
|
66
|
+
| `q` / `Esc` | Quit |
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Install dependencies
|
|
72
|
+
bun install
|
|
73
|
+
|
|
74
|
+
# Run in development
|
|
75
|
+
bun run cli
|
|
76
|
+
|
|
77
|
+
# Watch mode
|
|
78
|
+
bun run cli:watch
|
|
79
|
+
|
|
80
|
+
# Build
|
|
81
|
+
cd /Users/abelfubu/dev/diffview && bun run build
|
|
82
|
+
|
|
83
|
+
# Run tests
|
|
84
|
+
bun test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Project Structure
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
.
|
|
91
|
+
├── src/
|
|
92
|
+
│ ├── cli.tsx # CLI entrypoint
|
|
93
|
+
│ ├── components/ # React/TUI components
|
|
94
|
+
│ ├── diff-*.ts # Diff parsing, cursor, copy utilities
|
|
95
|
+
│ ├── themes.ts # Theme definitions
|
|
96
|
+
│ ├── store.ts # Persistent state
|
|
97
|
+
│ └── parsers/ # Tree-sitter syntax parsers
|
|
98
|
+
├── docs/ # ADRs and feature docs
|
|
99
|
+
├── dist/ # Compiled output
|
|
100
|
+
└── package.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Docs
|
|
104
|
+
|
|
105
|
+
- [`docs/adr-cursor-selection.md`](docs/adr-cursor-selection.md)
|
|
106
|
+
- [`docs/transparent-background.md`](docs/transparent-background.md)
|
|
107
|
+
- [`CONTEXT.md`](CONTEXT.md) — domain glossary
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -43,7 +43,7 @@ describe("App copy notification", () => {
|
|
|
43
43
|
});
|
|
44
44
|
it("shows a transient success notification after copying selected lines", async () => {
|
|
45
45
|
const parsedFiles = [createParsedFile("a.ts")];
|
|
46
|
-
testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
|
|
46
|
+
testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles, copyNotificationDuration: 100 }), {
|
|
47
47
|
width: 120,
|
|
48
48
|
height: 16,
|
|
49
49
|
});
|
|
@@ -61,15 +61,19 @@ describe("App copy notification", () => {
|
|
|
61
61
|
testSetup.mockInput.pressKey("j");
|
|
62
62
|
await testSetup.renderOnce();
|
|
63
63
|
});
|
|
64
|
+
testSetup.mockInput.pressKey("y");
|
|
65
|
+
// The toast registers a post-render callback from an effect, and on the
|
|
66
|
+
// single-threaded Linux test renderer the state update isn't flushed in
|
|
67
|
+
// time for the next renderOnce(). Yield briefly before rendering.
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
64
69
|
await act(async () => {
|
|
65
|
-
testSetup.mockInput.pressKey("y");
|
|
66
70
|
await testSetup.renderOnce();
|
|
67
71
|
});
|
|
68
72
|
const frame = testSetup.captureCharFrame();
|
|
69
73
|
expect(frame).toContain("Copied 2 lines");
|
|
70
74
|
expect(frame).toContain("✓");
|
|
71
75
|
// Wait for the auto-hide timeout to clear the notification.
|
|
72
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
73
77
|
await act(async () => {
|
|
74
78
|
await testSetup.renderOnce();
|
|
75
79
|
});
|
package/dist/cli.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import * as React from "react";
|
|
|
4
4
|
import { type ParsedFile } from "./diff-utils.js";
|
|
5
5
|
export interface AppProps {
|
|
6
6
|
parsedFiles: ParsedFile[];
|
|
7
|
+
/** Duration (ms) the copy notification toast stays visible. Defaults to 2000. */
|
|
8
|
+
copyNotificationDuration?: number;
|
|
7
9
|
}
|
|
8
|
-
export declare function App({ parsedFiles }: AppProps): React.ReactNode;
|
|
10
|
+
export declare function App({ parsedFiles, copyNotificationDuration }: AppProps): React.ReactNode;
|
|
9
11
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;IAC1B,iFAAiF;IACjF,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAClC;AAKD,wBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,wBAA+B,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,SAAS,CAuzB/F"}
|
package/dist/cli.js
CHANGED
|
@@ -110,7 +110,7 @@ class ScrollAcceleration {
|
|
|
110
110
|
}
|
|
111
111
|
const SIDEBAR_GAP = 2;
|
|
112
112
|
const APP_HORIZONTAL_PADDING = 2;
|
|
113
|
-
export function App({ parsedFiles }) {
|
|
113
|
+
export function App({ parsedFiles, copyNotificationDuration = 2000 }) {
|
|
114
114
|
const { width: initialWidth, height: initialHeight } = useTerminalDimensions();
|
|
115
115
|
const [width, setWidth] = React.useState(initialWidth);
|
|
116
116
|
const [_terminalHeight, setTerminalHeight] = React.useState(initialHeight);
|
|
@@ -231,7 +231,7 @@ export function App({ parsedFiles }) {
|
|
|
231
231
|
copyNotificationTimeoutRef.current = setTimeout(() => {
|
|
232
232
|
setCopyNotification(null);
|
|
233
233
|
copyNotificationTimeoutRef.current = null;
|
|
234
|
-
},
|
|
234
|
+
}, copyNotificationDuration);
|
|
235
235
|
return () => {
|
|
236
236
|
if (copyNotificationTimeoutRef.current) {
|
|
237
237
|
clearTimeout(copyNotificationTimeoutRef.current);
|
|
@@ -1 +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,
|
|
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,EAAqB,KAAK,cAAc,EAAmD,MAAM,gBAAgB,CAAA;AAIxH,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;AAuHD,eAAO,MAAM,QAAQ,mFAiKnB,CAAA"}
|
|
@@ -3,9 +3,55 @@ import { jsx as _jsx } from "@opentuah/react/jsx-runtime";
|
|
|
3
3
|
// Wraps opentui's <diff> element with theme-aware colors and syntax styles.
|
|
4
4
|
// Supports split and unified view modes with line numbers.
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import {
|
|
6
|
+
import { RGBA, SyntaxStyle } from "@opentuah/core";
|
|
7
7
|
import { getSyntaxTheme, getResolvedTheme, rgbaToHex } from "../themes.js";
|
|
8
8
|
import { balanceDelimiters } from "../balance-delimiters.js";
|
|
9
|
+
function getSideLineColorConfig(side, line) {
|
|
10
|
+
if (!side)
|
|
11
|
+
return null;
|
|
12
|
+
const { gutter, content } = side.getLineColors();
|
|
13
|
+
const g = gutter.get(line);
|
|
14
|
+
const c = content.get(line);
|
|
15
|
+
if (!g && !c)
|
|
16
|
+
return null;
|
|
17
|
+
const config = {};
|
|
18
|
+
if (g)
|
|
19
|
+
config.gutter = g;
|
|
20
|
+
if (c)
|
|
21
|
+
config.content = c;
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
function snapshotLineColors(diffRenderable, line) {
|
|
25
|
+
const { leftSide, rightSide } = diffRenderable;
|
|
26
|
+
return {
|
|
27
|
+
left: getSideLineColorConfig(leftSide, line),
|
|
28
|
+
right: getSideLineColorConfig(rightSide, line),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isTransparent(color) {
|
|
32
|
+
if (!color)
|
|
33
|
+
return true;
|
|
34
|
+
if (typeof color === "string")
|
|
35
|
+
return color.toLowerCase() === "transparent" || color === "#00000000";
|
|
36
|
+
return color.a === 0;
|
|
37
|
+
}
|
|
38
|
+
function restoreSideLineColor(side, line, color) {
|
|
39
|
+
if (!side)
|
|
40
|
+
return;
|
|
41
|
+
const hasVisibleColor = color && ((color.gutter && !isTransparent(color.gutter)) ||
|
|
42
|
+
(color.content && !isTransparent(color.content)));
|
|
43
|
+
if (hasVisibleColor) {
|
|
44
|
+
side.setLineColor(line, color);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
side.clearLineColor(line);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function restoreLineColors(diffRenderable, line, base) {
|
|
51
|
+
const { leftSide, rightSide } = diffRenderable;
|
|
52
|
+
restoreSideLineColor(leftSide, line, base.left);
|
|
53
|
+
restoreSideLineColor(rightSide, line, base.right);
|
|
54
|
+
}
|
|
9
55
|
function getLuminance(color) {
|
|
10
56
|
return color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722;
|
|
11
57
|
}
|
|
@@ -83,36 +129,51 @@ export const DiffView = React.forwardRef(function DiffView({ diff, view, filetyp
|
|
|
83
129
|
const activeSelectionColor = React.useMemo(() => {
|
|
84
130
|
return selectionColor ?? "#264F78";
|
|
85
131
|
}, [selectionColor]);
|
|
86
|
-
// Track previously-applied highlights so we can clear only our own overrides.
|
|
87
132
|
const prevCursorRef = React.useRef(null);
|
|
88
133
|
const prevSelectionRef = React.useRef(null);
|
|
134
|
+
// Reset highlight tracking when the underlying diff surface is rebuilt so
|
|
135
|
+
// we do not try to clear lines on a stale renderable.
|
|
136
|
+
React.useEffect(() => {
|
|
137
|
+
prevCursorRef.current = null;
|
|
138
|
+
prevSelectionRef.current = null;
|
|
139
|
+
}, [diff, view, themeName]);
|
|
89
140
|
// Apply cursor line and selection highlights to the underlying DiffRenderable.
|
|
90
141
|
React.useEffect(() => {
|
|
91
142
|
const diffRenderable = diffRef.current;
|
|
92
143
|
if (!diffRenderable)
|
|
93
144
|
return;
|
|
94
|
-
//
|
|
145
|
+
// Restore previous cursor override.
|
|
95
146
|
if (prevCursorRef.current) {
|
|
96
|
-
diffRenderable.
|
|
147
|
+
restoreLineColors(diffRenderable, prevCursorRef.current.line, prevCursorRef.current.base);
|
|
97
148
|
}
|
|
98
|
-
//
|
|
149
|
+
// Restore previous selection overrides.
|
|
99
150
|
if (prevSelectionRef.current) {
|
|
100
|
-
|
|
151
|
+
const { start, end, base } = prevSelectionRef.current;
|
|
152
|
+
for (let line = start; line <= end; line++) {
|
|
153
|
+
restoreLineColors(diffRenderable, line, base.get(line) ?? { left: null, right: null });
|
|
154
|
+
}
|
|
101
155
|
}
|
|
102
156
|
prevCursorRef.current = null;
|
|
103
157
|
prevSelectionRef.current = null;
|
|
104
158
|
if (!focused)
|
|
105
159
|
return;
|
|
160
|
+
// Snapshot the cursor line's base color before applying any override.
|
|
161
|
+
const cursorBase = snapshotLineColors(diffRenderable, cursorLine);
|
|
162
|
+
// Snapshot and apply selection range.
|
|
106
163
|
if (selection) {
|
|
107
164
|
const start = Math.min(selection.start, selection.end);
|
|
108
165
|
const end = Math.max(selection.start, selection.end);
|
|
109
166
|
if (end >= start) {
|
|
167
|
+
const selectionBase = new Map();
|
|
168
|
+
for (let line = start; line <= end; line++) {
|
|
169
|
+
selectionBase.set(line, snapshotLineColors(diffRenderable, line));
|
|
170
|
+
}
|
|
110
171
|
diffRenderable.highlightLines(start, end, activeSelectionColor);
|
|
111
|
-
prevSelectionRef.current = { start, end, color: activeSelectionColor };
|
|
172
|
+
prevSelectionRef.current = { start, end, color: activeSelectionColor, base: selectionBase };
|
|
112
173
|
}
|
|
113
174
|
}
|
|
114
175
|
diffRenderable.setLineColor(cursorLine, activeCursorColor);
|
|
115
|
-
prevCursorRef.current = { line: cursorLine,
|
|
176
|
+
prevCursorRef.current = { line: cursorLine, base: cursorBase };
|
|
116
177
|
}, [focused, cursorLine, selection, activeCursorColor, activeSelectionColor]);
|
|
117
178
|
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
179
|
// `addedBg`/`removedBg` are used by opentui as the base colors for word-level highlights.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-view.test.d.ts","sourceRoot":"","sources":["../../src/components/diff-view.test.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diff-view.test.d.ts","sourceRoot":"","sources":["../../src/components/diff-view.test.tsx"],"names":[],"mappings":"AAUA,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,wBAAwB,EAAE,OAAO,GAAG,SAAS,CAAA;CAClD"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "@opentuah/react/jsx-runtime";
|
|
|
2
2
|
// Tests for DiffView theme reactivity when switching themes at runtime.
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
|
+
import { act } from "react";
|
|
5
6
|
import { testRender } from "@opentuah/react/test-utils";
|
|
6
7
|
import { getDataPaths } from "@opentuah/core";
|
|
7
8
|
import { DiffView } from "./diff-view.js";
|
|
@@ -186,6 +187,19 @@ describe("DiffView", () => {
|
|
|
186
187
|
}
|
|
187
188
|
useAppStore.setState({ themeName: "github" });
|
|
188
189
|
});
|
|
190
|
+
it("uses transparent background for context lines when transparentBackground is enabled", async () => {
|
|
191
|
+
testSetup = await setupTest(_jsx(DiffView, { diff: sampleDiff, view: "unified", filetype: "txt", themeName: "github", transparentBackground: true }), {
|
|
192
|
+
width: 80,
|
|
193
|
+
height: 8,
|
|
194
|
+
});
|
|
195
|
+
await testSetup.renderOnce();
|
|
196
|
+
const frame = testSetup.captureSpans();
|
|
197
|
+
const contextLine = getLineWithToken(frame, "keep");
|
|
198
|
+
const contextSpan = contextLine.spans.find((span) => span.text === "keep");
|
|
199
|
+
expect(contextSpan).toBeDefined();
|
|
200
|
+
const bg = Array.from(contextSpan.bg.buffer);
|
|
201
|
+
expect(bg[3]).toBeCloseTo(0, 4);
|
|
202
|
+
});
|
|
189
203
|
it("updates diff background colors after theme switch", async () => {
|
|
190
204
|
useAppStore.setState({ themeName: "github" });
|
|
191
205
|
testSetup = await setupTest(_jsx(ThemeToggleHarness, {}), {
|
|
@@ -293,6 +307,46 @@ describe("DiffView", () => {
|
|
|
293
307
|
expect.closeTo(86 / 255, 4),
|
|
294
308
|
]);
|
|
295
309
|
});
|
|
310
|
+
it("restores the diff background color when the cursor moves past a line", async () => {
|
|
311
|
+
let setCursorLine = () => { };
|
|
312
|
+
function CursorMoveHarness() {
|
|
313
|
+
const [line, setLine] = React.useState(2);
|
|
314
|
+
setCursorLine = setLine;
|
|
315
|
+
return (_jsx(DiffView, { diff: sampleDiff, view: "unified", filetype: "txt", themeName: "github", focused: true, cursorLine: line, cursorColor: "#123456" }));
|
|
316
|
+
}
|
|
317
|
+
testSetup = await setupTest(_jsx(CursorMoveHarness, {}), {
|
|
318
|
+
width: 80,
|
|
319
|
+
height: 8,
|
|
320
|
+
});
|
|
321
|
+
await testSetup.renderOnce();
|
|
322
|
+
// Capture the original context background before the cursor moves over it.
|
|
323
|
+
const frameBefore = testSetup.captureSpans();
|
|
324
|
+
const contextLineBefore = getLineWithToken(frameBefore, "keep");
|
|
325
|
+
expect(contextLineBefore).toBeDefined();
|
|
326
|
+
const contextBg = Array.from(contextLineBefore.spans[0].bg.buffer).slice(0, 3);
|
|
327
|
+
// Move the cursor from the added line to the context line.
|
|
328
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
329
|
+
act(() => setCursorLine(3));
|
|
330
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = false;
|
|
331
|
+
await testSetup.renderOnce();
|
|
332
|
+
const frameAfter = testSetup.captureSpans();
|
|
333
|
+
const addedLine = getLineWithToken(frameAfter, "new");
|
|
334
|
+
const removedLine = getLineWithToken(frameAfter, "old");
|
|
335
|
+
expect(addedLine).toBeDefined();
|
|
336
|
+
expect(removedLine).toBeDefined();
|
|
337
|
+
const addedSpan = addedLine.spans.find((span) => span.text === "new");
|
|
338
|
+
const removedSpan = removedLine.spans.find((span) => span.text === "old");
|
|
339
|
+
expect(addedSpan).toBeDefined();
|
|
340
|
+
expect(removedSpan).toBeDefined();
|
|
341
|
+
const addedBg = Array.from(addedSpan.bg.buffer).slice(0, 3);
|
|
342
|
+
const removedBg = Array.from(removedSpan.bg.buffer).slice(0, 3);
|
|
343
|
+
// After moving the cursor, the added and removed lines should show their
|
|
344
|
+
// original diff backgrounds, not the default context background.
|
|
345
|
+
expect(addedBg).not.toEqual(contextBg);
|
|
346
|
+
expect(removedBg).not.toEqual(contextBg);
|
|
347
|
+
// They should also be different from each other (added vs removed).
|
|
348
|
+
expect(addedBg).not.toEqual(removedBg);
|
|
349
|
+
});
|
|
296
350
|
it("does not highlight a cursor line when not focused", async () => {
|
|
297
351
|
testSetup = await setupTest(_jsx(DiffView, { diff: sampleDiff, view: "unified", filetype: "txt", themeName: "github", focused: false, cursorLine: 2, cursorColor: "#123456" }), {
|
|
298
352
|
width: 80,
|
package/dist/components/toast.js
CHANGED
|
@@ -10,7 +10,7 @@ import {} from "../themes.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export function Toast({ message, type, title, theme, transparentBackground }) {
|
|
12
12
|
const renderer = useRenderer();
|
|
13
|
-
React.
|
|
13
|
+
React.useLayoutEffect(() => {
|
|
14
14
|
const borderColor = RGBA.fromHex(type === "success" ? "#2d8a47" : "#c53b53");
|
|
15
15
|
const bgColor = transparentBackground ? RGBA.fromInts(0, 0, 0, 0) : theme.background;
|
|
16
16
|
const fgColor = theme.text;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/theme.json",
|
|
3
|
+
"defs": {
|
|
4
|
+
"darkBg": "#0f0f0f",
|
|
5
|
+
"darkBgPanel": "#15141b",
|
|
6
|
+
"darkBorder": "#2d2d2d",
|
|
7
|
+
"darkFgMuted": "#6d6d6d",
|
|
8
|
+
"darkFg": "#edecee",
|
|
9
|
+
"purple": "#a277ff",
|
|
10
|
+
"pink": "#f694ff",
|
|
11
|
+
"blue": "#82e2ff",
|
|
12
|
+
"red": "#ff6767",
|
|
13
|
+
"orange": "#ffca85",
|
|
14
|
+
"cyan": "#61ffca",
|
|
15
|
+
"green": "#9dff65"
|
|
16
|
+
},
|
|
17
|
+
"theme": {
|
|
18
|
+
"primary": "purple",
|
|
19
|
+
"secondary": "pink",
|
|
20
|
+
"accent": "purple",
|
|
21
|
+
"error": "red",
|
|
22
|
+
"warning": "orange",
|
|
23
|
+
"success": "cyan",
|
|
24
|
+
"info": "purple",
|
|
25
|
+
"text": "darkFg",
|
|
26
|
+
"textMuted": "darkFgMuted",
|
|
27
|
+
"background": "darkBg",
|
|
28
|
+
"backgroundPanel": "darkBgPanel",
|
|
29
|
+
"backgroundElement": "darkBgPanel",
|
|
30
|
+
"border": "darkBorder",
|
|
31
|
+
"borderActive": "darkFgMuted",
|
|
32
|
+
"borderSubtle": "darkBorder",
|
|
33
|
+
"diffAdded": "cyan",
|
|
34
|
+
"diffRemoved": "red",
|
|
35
|
+
"diffContext": "darkFgMuted",
|
|
36
|
+
"diffHunkHeader": "darkFgMuted",
|
|
37
|
+
"diffHighlightAdded": "cyan",
|
|
38
|
+
"diffHighlightRemoved": "red",
|
|
39
|
+
"diffAddedBg": "#354933",
|
|
40
|
+
"diffRemovedBg": "#3f191a",
|
|
41
|
+
"diffContextBg": "darkBgPanel",
|
|
42
|
+
"diffLineNumber": "darkBorder",
|
|
43
|
+
"diffAddedLineNumberBg": "#162620",
|
|
44
|
+
"diffRemovedLineNumberBg": "#26161a",
|
|
45
|
+
"markdownText": "darkFg",
|
|
46
|
+
"markdownHeading": "purple",
|
|
47
|
+
"markdownLink": "pink",
|
|
48
|
+
"markdownLinkText": "purple",
|
|
49
|
+
"markdownCode": "cyan",
|
|
50
|
+
"markdownBlockQuote": "darkFgMuted",
|
|
51
|
+
"markdownEmph": "orange",
|
|
52
|
+
"markdownStrong": "purple",
|
|
53
|
+
"markdownHorizontalRule": "darkFgMuted",
|
|
54
|
+
"markdownListItem": "purple",
|
|
55
|
+
"markdownListEnumeration": "purple",
|
|
56
|
+
"markdownImage": "pink",
|
|
57
|
+
"markdownImageText": "purple",
|
|
58
|
+
"markdownCodeBlock": "darkFg",
|
|
59
|
+
"syntaxComment": "darkFgMuted",
|
|
60
|
+
"syntaxKeyword": "pink",
|
|
61
|
+
"syntaxFunction": "purple",
|
|
62
|
+
"syntaxVariable": "purple",
|
|
63
|
+
"syntaxString": "cyan",
|
|
64
|
+
"syntaxNumber": "green",
|
|
65
|
+
"syntaxType": "purple",
|
|
66
|
+
"syntaxOperator": "pink",
|
|
67
|
+
"syntaxPunctuation": "darkFg"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/theme.json",
|
|
3
|
+
"defs": {
|
|
4
|
+
"darkBg": "#0B0E14",
|
|
5
|
+
"darkBgAlt": "#0D1017",
|
|
6
|
+
"darkLine": "#11151C",
|
|
7
|
+
"darkPanel": "#0F131A",
|
|
8
|
+
"darkFg": "#BFBDB6",
|
|
9
|
+
"darkFgMuted": "#565B66",
|
|
10
|
+
"darkGutter": "#6C7380",
|
|
11
|
+
"darkTag": "#39BAE6",
|
|
12
|
+
"darkFunc": "#FFB454",
|
|
13
|
+
"darkEntity": "#59C2FF",
|
|
14
|
+
"darkString": "#AAD94C",
|
|
15
|
+
"darkRegexp": "#95E6CB",
|
|
16
|
+
"darkMarkup": "#F07178",
|
|
17
|
+
"darkKeyword": "#FF8F40",
|
|
18
|
+
"darkSpecial": "#E6B673",
|
|
19
|
+
"darkComment": "#ACB6BF",
|
|
20
|
+
"darkConstant": "#D2A6FF",
|
|
21
|
+
"darkOperator": "#F29668",
|
|
22
|
+
"darkAdded": "#7FD962",
|
|
23
|
+
"darkRemoved": "#F26D78",
|
|
24
|
+
"darkAccent": "#E6B450",
|
|
25
|
+
"darkError": "#D95757",
|
|
26
|
+
"darkIndentActive": "#6C7380"
|
|
27
|
+
},
|
|
28
|
+
"theme": {
|
|
29
|
+
"primary": "darkEntity",
|
|
30
|
+
"secondary": "darkConstant",
|
|
31
|
+
"accent": "darkAccent",
|
|
32
|
+
"error": "darkError",
|
|
33
|
+
"warning": "darkSpecial",
|
|
34
|
+
"success": "darkAdded",
|
|
35
|
+
"info": "darkTag",
|
|
36
|
+
"text": "darkFg",
|
|
37
|
+
"textMuted": "darkFgMuted",
|
|
38
|
+
"background": "darkBg",
|
|
39
|
+
"backgroundPanel": "darkPanel",
|
|
40
|
+
"backgroundElement": "darkBgAlt",
|
|
41
|
+
"border": "darkGutter",
|
|
42
|
+
"borderActive": "darkIndentActive",
|
|
43
|
+
"borderSubtle": "darkLine",
|
|
44
|
+
"diffAdded": "darkAdded",
|
|
45
|
+
"diffRemoved": "darkRemoved",
|
|
46
|
+
"diffContext": "darkComment",
|
|
47
|
+
"diffHunkHeader": "darkComment",
|
|
48
|
+
"diffHighlightAdded": "darkString",
|
|
49
|
+
"diffHighlightRemoved": "darkMarkup",
|
|
50
|
+
"diffAddedBg": "#20303b",
|
|
51
|
+
"diffRemovedBg": "#37222c",
|
|
52
|
+
"diffContextBg": "darkPanel",
|
|
53
|
+
"diffLineNumber": "darkGutter",
|
|
54
|
+
"diffAddedLineNumberBg": "#1b2b34",
|
|
55
|
+
"diffRemovedLineNumberBg": "#2d1f26",
|
|
56
|
+
"markdownText": "darkFg",
|
|
57
|
+
"markdownHeading": "darkConstant",
|
|
58
|
+
"markdownLink": "darkEntity",
|
|
59
|
+
"markdownLinkText": "darkTag",
|
|
60
|
+
"markdownCode": "darkString",
|
|
61
|
+
"markdownBlockQuote": "darkSpecial",
|
|
62
|
+
"markdownEmph": "darkSpecial",
|
|
63
|
+
"markdownStrong": "darkFunc",
|
|
64
|
+
"markdownHorizontalRule": "darkFgMuted",
|
|
65
|
+
"markdownListItem": "darkEntity",
|
|
66
|
+
"markdownListEnumeration": "darkTag",
|
|
67
|
+
"markdownImage": "darkEntity",
|
|
68
|
+
"markdownImageText": "darkTag",
|
|
69
|
+
"markdownCodeBlock": "darkFg",
|
|
70
|
+
"syntaxComment": "darkComment",
|
|
71
|
+
"syntaxKeyword": "darkKeyword",
|
|
72
|
+
"syntaxFunction": "darkFunc",
|
|
73
|
+
"syntaxVariable": "darkEntity",
|
|
74
|
+
"syntaxString": "darkString",
|
|
75
|
+
"syntaxNumber": "darkConstant",
|
|
76
|
+
"syntaxType": "darkSpecial",
|
|
77
|
+
"syntaxOperator": "darkOperator",
|
|
78
|
+
"syntaxPunctuation": "darkFg"
|
|
79
|
+
}
|
|
80
|
+
}
|