@assistant-ui/react-ink 0.0.6 → 0.0.7

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 (66) hide show
  1. package/README.md +1 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/primitives/composer/ComposerSend.js +1 -1
  7. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  8. package/dist/primitives/diff/DiffContent.d.ts +22 -0
  9. package/dist/primitives/diff/DiffContent.d.ts.map +1 -0
  10. package/dist/primitives/diff/DiffContent.js +40 -0
  11. package/dist/primitives/diff/DiffContent.js.map +1 -0
  12. package/dist/primitives/diff/DiffContext.d.ts +8 -0
  13. package/dist/primitives/diff/DiffContext.d.ts.map +1 -0
  14. package/dist/primitives/diff/DiffContext.js +11 -0
  15. package/dist/primitives/diff/DiffContext.js.map +1 -0
  16. package/dist/primitives/diff/DiffHeader.d.ts +10 -0
  17. package/dist/primitives/diff/DiffHeader.d.ts.map +1 -0
  18. package/dist/primitives/diff/DiffHeader.js +18 -0
  19. package/dist/primitives/diff/DiffHeader.js.map +1 -0
  20. package/dist/primitives/diff/DiffLine.d.ts +13 -0
  21. package/dist/primitives/diff/DiffLine.d.ts.map +1 -0
  22. package/dist/primitives/diff/DiffLine.js +20 -0
  23. package/dist/primitives/diff/DiffLine.js.map +1 -0
  24. package/dist/primitives/diff/DiffRoot.d.ts +12 -0
  25. package/dist/primitives/diff/DiffRoot.d.ts.map +1 -0
  26. package/dist/primitives/diff/DiffRoot.js +8 -0
  27. package/dist/primitives/diff/DiffRoot.js.map +1 -0
  28. package/dist/primitives/diff/DiffStats.d.ts +10 -0
  29. package/dist/primitives/diff/DiffStats.d.ts.map +1 -0
  30. package/dist/primitives/diff/DiffStats.js +12 -0
  31. package/dist/primitives/diff/DiffStats.js.map +1 -0
  32. package/dist/primitives/diff/DiffView.d.ts +13 -0
  33. package/dist/primitives/diff/DiffView.d.ts.map +1 -0
  34. package/dist/primitives/diff/DiffView.js +76 -0
  35. package/dist/primitives/diff/DiffView.js.map +1 -0
  36. package/dist/primitives/diff/diff-utils.d.ts +9 -0
  37. package/dist/primitives/diff/diff-utils.d.ts.map +1 -0
  38. package/dist/primitives/diff/diff-utils.js +125 -0
  39. package/dist/primitives/diff/diff-utils.js.map +1 -0
  40. package/dist/primitives/diff/index.d.ts +7 -0
  41. package/dist/primitives/diff/index.d.ts.map +1 -0
  42. package/dist/primitives/diff/index.js +6 -0
  43. package/dist/primitives/diff/index.js.map +1 -0
  44. package/dist/primitives/diff/types.d.ts +24 -0
  45. package/dist/primitives/diff/types.d.ts.map +1 -0
  46. package/dist/primitives/diff/types.js +2 -0
  47. package/dist/primitives/diff/types.js.map +1 -0
  48. package/dist/primitives/diff.d.ts +2 -0
  49. package/dist/primitives/diff.d.ts.map +1 -0
  50. package/dist/primitives/diff.js +2 -0
  51. package/dist/primitives/diff.js.map +1 -0
  52. package/package.json +9 -7
  53. package/src/index.ts +2 -0
  54. package/src/primitives/composer/ComposerSend.tsx +1 -1
  55. package/src/primitives/diff/DiffContent.tsx +77 -0
  56. package/src/primitives/diff/DiffContext.tsx +18 -0
  57. package/src/primitives/diff/DiffHeader.tsx +38 -0
  58. package/src/primitives/diff/DiffLine.tsx +42 -0
  59. package/src/primitives/diff/DiffRoot.tsx +25 -0
  60. package/src/primitives/diff/DiffStats.tsx +22 -0
  61. package/src/primitives/diff/DiffView.test.tsx +340 -0
  62. package/src/primitives/diff/DiffView.tsx +204 -0
  63. package/src/primitives/diff/diff-utils.ts +149 -0
  64. package/src/primitives/diff/index.ts +25 -0
  65. package/src/primitives/diff/types.ts +28 -0
  66. package/src/primitives/diff.ts +1 -0
@@ -0,0 +1,204 @@
1
+ import { type ComponentProps, useMemo } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { DiffContent } from "./DiffContent";
4
+ import { useDiffContext } from "./DiffContext";
5
+ import { DiffRoot } from "./DiffRoot";
6
+ import { computeDiff, parsePatch } from "./diff-utils";
7
+ import type {
8
+ DiffFileInput,
9
+ FoldedRegion,
10
+ ParsedFile,
11
+ ParsedLine,
12
+ } from "./types";
13
+
14
+ export type DiffViewProps = Omit<ComponentProps<typeof Box>, "children"> & {
15
+ patch?: string | undefined;
16
+ oldFile?: DiffFileInput | undefined;
17
+ newFile?: DiffFileInput | undefined;
18
+ showLineNumbers?: boolean | undefined;
19
+ contextLines?: number | undefined;
20
+ maxLines?: number | undefined;
21
+ };
22
+
23
+ interface DiffViewInnerProps {
24
+ showLineNumbers: boolean | undefined;
25
+ contextLines: number | undefined;
26
+ maxLines: number | undefined;
27
+ }
28
+
29
+ const INDICATOR: Record<string, string> = {
30
+ add: "+",
31
+ del: "-",
32
+ normal: " ",
33
+ };
34
+
35
+ const isDevNull = (n: string | undefined) => !n || n === "/dev/null";
36
+
37
+ const StyledLine = ({
38
+ line,
39
+ showLineNumbers,
40
+ }: {
41
+ line: ParsedLine;
42
+ showLineNumbers: boolean;
43
+ }) => {
44
+ const lineNum =
45
+ line.type === "del"
46
+ ? line.oldLineNumber
47
+ : line.type === "add"
48
+ ? line.newLineNumber
49
+ : line.oldLineNumber;
50
+ const numStr = lineNum !== undefined ? String(lineNum) : "";
51
+ const padded = numStr.padStart(4);
52
+ const content = `${INDICATOR[line.type]} ${line.content}`;
53
+
54
+ return (
55
+ <Box>
56
+ {showLineNumbers && <Text dimColor>{padded} </Text>}
57
+ {line.type === "add" ? (
58
+ <Text color="green">{content}</Text>
59
+ ) : line.type === "del" ? (
60
+ <Text color="red">{content}</Text>
61
+ ) : (
62
+ <Text>{content}</Text>
63
+ )}
64
+ </Box>
65
+ );
66
+ };
67
+
68
+ const StyledFold = ({ region }: { region: FoldedRegion }) => (
69
+ <Text dimColor>{` --- ${region.hiddenCount} lines hidden ---`}</Text>
70
+ );
71
+
72
+ const DiffViewInner = ({
73
+ showLineNumbers,
74
+ contextLines,
75
+ maxLines,
76
+ }: DiffViewInnerProps) => {
77
+ const { files } = useDiffContext();
78
+ const shouldShowLineNumbers = showLineNumbers ?? true;
79
+
80
+ if (files.length === 0) {
81
+ return <Text dimColor>No diff content</Text>;
82
+ }
83
+
84
+ return (
85
+ <>
86
+ {files.map((file, i) => {
87
+ const renamed =
88
+ !isDevNull(file.oldName) &&
89
+ !isDevNull(file.newName) &&
90
+ file.oldName !== file.newName;
91
+ const displayName = isDevNull(file.newName)
92
+ ? file.oldName
93
+ : file.newName;
94
+
95
+ return (
96
+ <Box key={i} flexDirection="column">
97
+ <Box gap={1}>
98
+ {renamed ? (
99
+ <>
100
+ <Text bold dimColor>
101
+ {file.oldName}
102
+ </Text>
103
+ <Text dimColor>{"->"}</Text>
104
+ <Text bold>{file.newName}</Text>
105
+ </>
106
+ ) : (
107
+ <Text bold>{displayName}</Text>
108
+ )}
109
+ <Text color="green">+{file.additions}</Text>
110
+ <Text color="red">-{file.deletions}</Text>
111
+ </Box>
112
+ <DiffContent
113
+ fileIndex={i}
114
+ contextLines={contextLines}
115
+ maxLines={maxLines}
116
+ renderLine={({ line }) => (
117
+ <StyledLine
118
+ line={line}
119
+ showLineNumbers={shouldShowLineNumbers}
120
+ />
121
+ )}
122
+ renderFold={({ region }) => <StyledFold region={region} />}
123
+ />
124
+ {i < files.length - 1 && <Text> </Text>}
125
+ </Box>
126
+ );
127
+ })}
128
+ </>
129
+ );
130
+ };
131
+
132
+ const getDiffViewFiles = ({
133
+ patch,
134
+ oldFile,
135
+ newFile,
136
+ }: {
137
+ patch?: string | undefined;
138
+ oldFile?: DiffFileInput | undefined;
139
+ newFile?: DiffFileInput | undefined;
140
+ }): ParsedFile[] => {
141
+ if (patch) {
142
+ return parsePatch(patch);
143
+ }
144
+
145
+ if (!oldFile || !newFile) {
146
+ return [];
147
+ }
148
+
149
+ const { lines, additions, deletions } = computeDiff(
150
+ oldFile.content,
151
+ newFile.content,
152
+ );
153
+
154
+ return [
155
+ {
156
+ oldName: oldFile.name,
157
+ newName: newFile.name,
158
+ lines,
159
+ additions,
160
+ deletions,
161
+ },
162
+ ];
163
+ };
164
+
165
+ export const DiffView = ({
166
+ patch,
167
+ oldFile,
168
+ newFile,
169
+ showLineNumbers,
170
+ contextLines,
171
+ maxLines,
172
+ ...boxProps
173
+ }: DiffViewProps) => {
174
+ const oldContent = oldFile?.content;
175
+ const oldName = oldFile?.name;
176
+ const newContent = newFile?.content;
177
+ const newName = newFile?.name;
178
+
179
+ const files = useMemo(
180
+ () =>
181
+ getDiffViewFiles({
182
+ patch,
183
+ oldFile:
184
+ oldContent !== undefined
185
+ ? { content: oldContent, name: oldName }
186
+ : undefined,
187
+ newFile:
188
+ newContent !== undefined
189
+ ? { content: newContent, name: newName }
190
+ : undefined,
191
+ }),
192
+ [patch, oldContent, oldName, newContent, newName],
193
+ );
194
+
195
+ return (
196
+ <DiffRoot files={files} {...boxProps}>
197
+ <DiffViewInner
198
+ showLineNumbers={showLineNumbers}
199
+ contextLines={contextLines}
200
+ maxLines={maxLines}
201
+ />
202
+ </DiffRoot>
203
+ );
204
+ };
@@ -0,0 +1,149 @@
1
+ import { diffLines } from "diff";
2
+ import parseDiff from "parse-diff";
3
+ import type {
4
+ ParsedLine,
5
+ ParsedFile,
6
+ DisplayLine,
7
+ FoldedRegion,
8
+ } from "./types";
9
+
10
+ const NO_NEWLINE_MARKER = "\";
11
+
12
+ const stripTrailingCarriageReturn = (content: string) => {
13
+ return content.endsWith("\r") ? content.slice(0, -1) : content;
14
+ };
15
+
16
+ const parseChangeContent = (content: string) => {
17
+ const normalized = stripTrailingCarriageReturn(content);
18
+ if (normalized === NO_NEWLINE_MARKER) return null;
19
+ return stripTrailingCarriageReturn(normalized.slice(1));
20
+ };
21
+
22
+ const splitDiffLines = (value: string) => {
23
+ const normalized = value.endsWith("\n") ? value.slice(0, -1) : value;
24
+ if (normalized.length === 0) {
25
+ return value.length > 0 ? [""] : [];
26
+ }
27
+ return normalized.split("\n").map(stripTrailingCarriageReturn);
28
+ };
29
+
30
+ export function parsePatch(patch: string): ParsedFile[] {
31
+ const files = parseDiff(patch);
32
+ return files.map((file) => {
33
+ const lines: ParsedLine[] = [];
34
+ let additions = 0;
35
+ let deletions = 0;
36
+
37
+ for (const chunk of file.chunks) {
38
+ let oldLine = chunk.oldStart;
39
+ let newLine = chunk.newStart;
40
+
41
+ for (const change of chunk.changes) {
42
+ const content = parseChangeContent(change.content);
43
+ if (content === null) continue;
44
+
45
+ if (change.type === "add") {
46
+ additions++;
47
+ lines.push({
48
+ type: "add",
49
+ content,
50
+ newLineNumber: newLine++,
51
+ });
52
+ } else if (change.type === "del") {
53
+ deletions++;
54
+ lines.push({
55
+ type: "del",
56
+ content,
57
+ oldLineNumber: oldLine++,
58
+ });
59
+ } else {
60
+ lines.push({
61
+ type: "normal",
62
+ content,
63
+ oldLineNumber: oldLine++,
64
+ newLineNumber: newLine++,
65
+ });
66
+ }
67
+ }
68
+ }
69
+
70
+ return {
71
+ oldName: file.from,
72
+ newName: file.to,
73
+ lines,
74
+ additions,
75
+ deletions,
76
+ };
77
+ });
78
+ }
79
+
80
+ export function computeDiff(
81
+ oldContent: string,
82
+ newContent: string,
83
+ ): { lines: ParsedLine[]; additions: number; deletions: number } {
84
+ const changes = diffLines(oldContent, newContent);
85
+ const lines: ParsedLine[] = [];
86
+ let oldLine = 1;
87
+ let newLine = 1;
88
+ let additions = 0;
89
+ let deletions = 0;
90
+
91
+ for (const change of changes) {
92
+ for (const content of splitDiffLines(change.value)) {
93
+ if (change.added) {
94
+ additions++;
95
+ lines.push({ type: "add", content, newLineNumber: newLine++ });
96
+ } else if (change.removed) {
97
+ deletions++;
98
+ lines.push({ type: "del", content, oldLineNumber: oldLine++ });
99
+ } else {
100
+ lines.push({
101
+ type: "normal",
102
+ content,
103
+ oldLineNumber: oldLine++,
104
+ newLineNumber: newLine++,
105
+ });
106
+ }
107
+ }
108
+ }
109
+
110
+ return { lines, additions, deletions };
111
+ }
112
+
113
+ export function foldContext(
114
+ lines: ParsedLine[],
115
+ contextLines: number,
116
+ ): DisplayLine[] {
117
+ const ctx = Math.max(0, contextLines);
118
+ const keep = new Set<number>();
119
+
120
+ for (let i = 0; i < lines.length; i++) {
121
+ if (lines[i]!.type !== "normal") {
122
+ for (
123
+ let j = Math.max(0, i - ctx);
124
+ j <= Math.min(lines.length - 1, i + ctx);
125
+ j++
126
+ ) {
127
+ keep.add(j);
128
+ }
129
+ }
130
+ }
131
+
132
+ const result: DisplayLine[] = [];
133
+ let i = 0;
134
+ while (i < lines.length) {
135
+ if (keep.has(i)) {
136
+ result.push(lines[i]!);
137
+ i++;
138
+ } else {
139
+ let hiddenCount = 0;
140
+ while (i < lines.length && !keep.has(i)) {
141
+ hiddenCount++;
142
+ i++;
143
+ }
144
+ result.push({ type: "fold", hiddenCount } satisfies FoldedRegion);
145
+ }
146
+ }
147
+
148
+ return result;
149
+ }
@@ -0,0 +1,25 @@
1
+ export { DiffRoot as Root, type DiffRootProps as RootProps } from "./DiffRoot";
2
+ export {
3
+ DiffHeader as Header,
4
+ type DiffHeaderProps as HeaderProps,
5
+ } from "./DiffHeader";
6
+ export {
7
+ DiffContent as Content,
8
+ type DiffContentProps as ContentProps,
9
+ } from "./DiffContent";
10
+ export {
11
+ DiffLine as Line,
12
+ type DiffLineProps as LineProps,
13
+ } from "./DiffLine";
14
+ export {
15
+ DiffStats as Stats,
16
+ type DiffStatsProps as StatsProps,
17
+ } from "./DiffStats";
18
+ export type {
19
+ ParsedLine,
20
+ ParsedFile,
21
+ DiffFileInput,
22
+ DiffLineType,
23
+ DisplayLine,
24
+ FoldedRegion,
25
+ } from "./types";
@@ -0,0 +1,28 @@
1
+ export type DiffLineType = "add" | "del" | "normal";
2
+
3
+ export interface ParsedLine {
4
+ type: DiffLineType;
5
+ content: string;
6
+ oldLineNumber?: number;
7
+ newLineNumber?: number;
8
+ }
9
+
10
+ export interface ParsedFile {
11
+ oldName?: string | undefined;
12
+ newName?: string | undefined;
13
+ lines: ParsedLine[];
14
+ additions: number;
15
+ deletions: number;
16
+ }
17
+
18
+ export interface FoldedRegion {
19
+ type: "fold";
20
+ hiddenCount: number;
21
+ }
22
+
23
+ export type DisplayLine = ParsedLine | FoldedRegion;
24
+
25
+ export interface DiffFileInput {
26
+ content: string;
27
+ name?: string | undefined;
28
+ }
@@ -0,0 +1 @@
1
+ export * from "./diff/index";