@youtyan/code-viewer 0.1.16 → 0.1.17

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.
@@ -1,236 +0,0 @@
1
- export type DiffRange = {
2
- from?: string;
3
- to?: string;
4
- };
5
-
6
- export type ByteRange = {
7
- start: number;
8
- end: number;
9
- };
10
-
11
- export type ByteRangeParseResult =
12
- | { kind: 'range'; range: ByteRange }
13
- | { kind: 'invalid' }
14
- | { kind: 'unsatisfiable' };
15
-
16
- export type LineRangeResult = {
17
- lines: string[];
18
- total: number;
19
- complete: boolean;
20
- };
21
-
22
- export type LineOffsetIndex = {
23
- size: number;
24
- total: number;
25
- newlines: Uint32Array | Float64Array;
26
- };
27
-
28
- export type IndexedLineByteRange = {
29
- start: number;
30
- endExclusive: number;
31
- };
32
-
33
- export type BytesWithLineOffsetIndex = {
34
- bytes: Uint8Array;
35
- index: LineOffsetIndex;
36
- };
37
-
38
- export function isSameWorktreeRange(range: DiffRange): boolean {
39
- return range.from === 'worktree' && range.to === 'worktree';
40
- }
41
-
42
- export function parseHttpByteRange(header: string | null, size: number): ByteRangeParseResult {
43
- if (!header) return { kind: 'invalid' };
44
- if (size < 1) return { kind: 'unsatisfiable' };
45
- const match = header.match(/^bytes=(\d*)-(\d*)$/);
46
- if (!match) return { kind: 'invalid' };
47
- const [, rawStart, rawEnd] = match;
48
- if (!rawStart && !rawEnd) return { kind: 'invalid' };
49
- let start: number;
50
- let end: number;
51
- if (!rawStart) {
52
- const suffixLength = Number(rawEnd);
53
- if (!Number.isSafeInteger(suffixLength) || suffixLength < 1) return { kind: 'unsatisfiable' };
54
- start = Math.max(0, size - suffixLength);
55
- end = size - 1;
56
- } else {
57
- start = Number(rawStart);
58
- end = rawEnd ? Number(rawEnd) : size - 1;
59
- if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end)) return { kind: 'invalid' };
60
- if (end >= size) end = size - 1;
61
- }
62
- if (start < 0 || end < start || start >= size) return { kind: 'unsatisfiable' };
63
- return { kind: 'range', range: { start, end } };
64
- }
65
-
66
- export async function collectLineRangeFromStream(stream: ReadableStream<Uint8Array>, start: number, end: number): Promise<LineRangeResult> {
67
- const reader = stream.getReader();
68
- const decoder = new TextDecoder();
69
- const lines: string[] = [];
70
- let lineNo = 1;
71
- let pending = '';
72
- let hasMore = false;
73
-
74
- const pushLine = (line: string) => {
75
- if (line.endsWith('\r')) line = line.slice(0, -1);
76
- if (lineNo >= start && lineNo <= end) lines.push(line);
77
- else if (lineNo > end) hasMore = true;
78
- lineNo++;
79
- };
80
-
81
- while (!hasMore) {
82
- const chunk = await reader.read();
83
- if (chunk.done) break;
84
- pending += decoder.decode(chunk.value, { stream: true });
85
- let newline = pending.indexOf('\n');
86
- while (newline !== -1) {
87
- pushLine(pending.slice(0, newline));
88
- pending = pending.slice(newline + 1);
89
- if (hasMore) break;
90
- newline = pending.indexOf('\n');
91
- }
92
- }
93
- if (hasMore) {
94
- try { await reader.cancel(); } catch { /* best effort */ }
95
- return { lines, total: lineNo - 1, complete: false };
96
- }
97
- pending += decoder.decode();
98
- if (pending.length > 0) pushLine(pending);
99
- if (hasMore) return { lines, total: lineNo - 1, complete: false };
100
- return { lines, total: Math.max(0, lineNo - 1), complete: true };
101
- }
102
-
103
- export function buildLineOffsetIndex(bytes: Uint8Array): LineOffsetIndex {
104
- const builder = createLineOffsetIndexBuilder(bytes.length);
105
- for (let index = 0; index < bytes.length; index++) {
106
- if (bytes[index] === 10) builder.push(index);
107
- }
108
- const lastByte = bytes.length > 0 ? bytes[bytes.length - 1] : -1;
109
- return builder.finish(bytes.length, bytes.length > 0 && lastByte !== 10);
110
- }
111
-
112
- export async function buildLineOffsetIndexFromStream(stream: ReadableStream<Uint8Array>, size: number): Promise<LineOffsetIndex> {
113
- const reader = stream.getReader();
114
- const builder = createLineOffsetIndexBuilder(size);
115
- let offset = 0;
116
- let lastByte = -1;
117
- while (true) {
118
- const chunk = await reader.read();
119
- if (chunk.done) break;
120
- const bytes = chunk.value;
121
- for (let index = 0; index < bytes.length; index++) {
122
- const byte = bytes[index];
123
- if (byte === 10) builder.push(offset + index);
124
- lastByte = byte;
125
- }
126
- offset += bytes.length;
127
- }
128
- return builder.finish(offset, offset > 0 && lastByte !== 10);
129
- }
130
-
131
- export async function collectByteRangeFromStream(stream: ReadableStream<Uint8Array>, start: number, endExclusive: number): Promise<Uint8Array> {
132
- const reader = stream.getReader();
133
- const chunks: Uint8Array[] = [];
134
- let offset = 0;
135
- let total = 0;
136
- while (offset < endExclusive) {
137
- const chunk = await reader.read();
138
- if (chunk.done) break;
139
- const chunkStart = offset;
140
- const chunkEnd = offset + chunk.value.byteLength;
141
- if (chunkEnd > start && chunkStart < endExclusive) {
142
- const sliceStart = Math.max(0, start - chunkStart);
143
- const sliceEnd = Math.min(chunk.value.byteLength, endExclusive - chunkStart);
144
- const slice = chunk.value.subarray(sliceStart, sliceEnd);
145
- chunks.push(slice);
146
- total += slice.byteLength;
147
- }
148
- offset = chunkEnd;
149
- }
150
- try { await reader.cancel(); } catch { /* best effort */ }
151
- if (chunks.length === 1) return chunks[0];
152
- const bytes = new Uint8Array(total);
153
- let writeOffset = 0;
154
- for (const chunk of chunks) {
155
- bytes.set(chunk, writeOffset);
156
- writeOffset += chunk.byteLength;
157
- }
158
- return bytes;
159
- }
160
-
161
- export async function collectBytesWithLineOffsetIndexFromStream(stream: ReadableStream<Uint8Array>, sizeHint: number): Promise<BytesWithLineOffsetIndex> {
162
- const reader = stream.getReader();
163
- const builder = createLineOffsetIndexBuilder(sizeHint);
164
- const chunks: Uint8Array[] = [];
165
- let offset = 0;
166
- let lastByte = -1;
167
- while (true) {
168
- const chunk = await reader.read();
169
- if (chunk.done) break;
170
- const bytes = chunk.value;
171
- chunks.push(bytes);
172
- for (let index = 0; index < bytes.length; index++) {
173
- const byte = bytes[index];
174
- if (byte === 10) builder.push(offset + index);
175
- lastByte = byte;
176
- }
177
- offset += bytes.length;
178
- }
179
- const collected = new Uint8Array(offset);
180
- let writeOffset = 0;
181
- for (const chunk of chunks) {
182
- collected.set(chunk, writeOffset);
183
- writeOffset += chunk.byteLength;
184
- }
185
- return {
186
- bytes: collected,
187
- index: builder.finish(offset, offset > 0 && lastByte !== 10),
188
- };
189
- }
190
-
191
- function createLineOffsetIndexBuilder(size: number) {
192
- const useFloat64 = size > 0xffffffff;
193
- let capacity = 1024;
194
- let length = 0;
195
- let offsets: Uint32Array | Float64Array = useFloat64 ? new Float64Array(capacity) : new Uint32Array(capacity);
196
- const grow = () => {
197
- capacity *= 2;
198
- const next = useFloat64 ? new Float64Array(capacity) : new Uint32Array(capacity);
199
- next.set(offsets);
200
- offsets = next;
201
- };
202
- return {
203
- push(offset: number) {
204
- if (length >= capacity) grow();
205
- offsets[length++] = offset;
206
- },
207
- finish(totalSize: number, hasTrailingLine: boolean): LineOffsetIndex {
208
- return {
209
- size: totalSize,
210
- total: length + (hasTrailingLine ? 1 : 0),
211
- newlines: offsets.slice(0, length) as Uint32Array | Float64Array,
212
- };
213
- },
214
- };
215
- }
216
-
217
- export function lineByteRangeForIndex(index: LineOffsetIndex, start: number, end: number): IndexedLineByteRange | null {
218
- const normalizedStart = Math.max(1, Math.floor(start));
219
- const normalizedEnd = Math.max(normalizedStart, Math.floor(end));
220
- if (normalizedStart > index.total) return null;
221
- const lastLine = Math.min(normalizedEnd, index.total);
222
- const byteStart = normalizedStart <= 1 ? 0 : index.newlines[normalizedStart - 2] + 1;
223
- const byteEnd = lastLine <= index.newlines.length ? index.newlines[lastLine - 1] : index.size;
224
- return { start: byteStart, endExclusive: byteEnd };
225
- }
226
-
227
- export function collectLineRangeFromIndexedText(text: string, index: LineOffsetIndex, start: number, end: number): LineRangeResult {
228
- const normalizedStart = Math.max(1, Math.floor(start));
229
- const normalizedEnd = Math.max(normalizedStart, Math.floor(end));
230
- if (normalizedStart > index.total) return { lines: [], total: index.total, complete: true };
231
- const expectedLines = Math.min(normalizedEnd, index.total) - normalizedStart + 1;
232
- const lines = text.length
233
- ? text.split('\n').map(line => line.endsWith('\r') ? line.slice(0, -1) : line)
234
- : Array.from({ length: expectedLines }, () => '');
235
- return { lines, total: index.total, complete: end >= index.total };
236
- }
@@ -1,61 +0,0 @@
1
- interface BunFile extends globalThis.Blob {
2
- arrayBuffer(): Promise<ArrayBuffer>;
3
- slice(start?: number, end?: number, contentType?: string): BunFile;
4
- stream(): ReadableStream<Uint8Array<ArrayBuffer>>;
5
- text(): Promise<string>;
6
- }
7
-
8
- declare const Bun: {
9
- file(path: string): BunFile;
10
- spawn(args: string[], opts?: Record<string, unknown>): {
11
- kill(signal?: string): void;
12
- exited: Promise<number>;
13
- stdout?: ReadableStream<Uint8Array>;
14
- stderr?: ReadableStream<Uint8Array>;
15
- };
16
- spawnSync(args: string[], opts?: Record<string, unknown>): {
17
- exitCode: number;
18
- stdout: Uint8Array;
19
- stderr: Uint8Array;
20
- };
21
- serve(opts: {
22
- hostname?: string;
23
- port?: number;
24
- fetch(req: Request): Response | Promise<Response>;
25
- }): { port: number };
26
- };
27
-
28
- declare const process: {
29
- argv: string[];
30
- env: Record<string, string | undefined>;
31
- cwd(): string;
32
- platform: 'darwin' | 'win32' | string;
33
- on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void;
34
- exit(code?: number): never;
35
- };
36
-
37
- interface ImportMeta {
38
- dir: string;
39
- }
40
-
41
- declare module 'node:fs' {
42
- export function existsSync(path: string): boolean;
43
- export function readdirSync(path: string): string[];
44
- export function readFileSync(path: string): Buffer;
45
- export function readFileSync(path: string, encoding: BufferEncoding): string;
46
- export function realpathSync(path: string): string;
47
- export function statSync(path: string): { mtimeMs: number };
48
- export function watch(
49
- path: string,
50
- options: { persistent?: boolean },
51
- listener: (eventType: string, filename: string | Buffer | null) => void,
52
- ): unknown;
53
- }
54
-
55
- declare module 'node:path' {
56
- export function basename(path: string): string;
57
- export function extname(path: string): string;
58
- export function join(...parts: string[]): string;
59
- export function normalize(path: string): string;
60
- export function relative(from: string, to: string): string;
61
- }
@@ -1,104 +0,0 @@
1
- import type { FileSearchListResponse, GrepMatch } from '../types';
2
- import type { GitTreeEntry } from './git';
3
-
4
- export const GREP_DEFAULT_MAX = 200;
5
- export const GREP_ABSOLUTE_MAX = 500;
6
- export const GREP_MAX_FILE_BYTES = 2 * 1024 * 1024;
7
- export const FILE_SEARCH_ABSOLUTE_MAX = 50000;
8
-
9
- export function normalizeGrepMax(value: string | null): number {
10
- const parsed = Number(value || '');
11
- if (!Number.isInteger(parsed) || parsed <= 0) return GREP_DEFAULT_MAX;
12
- return Math.min(parsed, GREP_ABSOLUTE_MAX);
13
- }
14
-
15
- export function isSkippableSearchPath(path: string, omitDirNames: string[] = []): boolean {
16
- const omitDirs = new Set(omitDirNames.map(name => name.toLowerCase()));
17
- return path.split(/[\\/]+/).some(part => {
18
- const lower = part.toLowerCase();
19
- return lower === '.git' || omitDirs.has(lower);
20
- });
21
- }
22
-
23
- export function fixedStringLineMatches(path: string, text: string, query: string, max: number): GrepMatch[] {
24
- const needle = query.toLowerCase();
25
- if (!needle) return [];
26
- const matches: GrepMatch[] = [];
27
- const lines = text.split('\n');
28
- for (let i = 0; i < lines.length && matches.length < max; i++) {
29
- const line = lines[i];
30
- const column = line.toLowerCase().indexOf(needle);
31
- if (column < 0) continue;
32
- matches.push({
33
- path,
34
- line: i + 1,
35
- column: column + 1,
36
- preview: line.slice(0, 500),
37
- });
38
- }
39
- return matches;
40
- }
41
-
42
- export function buildFileSearchList(ref: string, generation: number, entries: GitTreeEntry[]): FileSearchListResponse {
43
- const files = entries
44
- .filter((entry): entry is GitTreeEntry & { type: 'blob' | 'commit' } => entry.type === 'blob' || entry.type === 'commit')
45
- .slice(0, FILE_SEARCH_ABSOLUTE_MAX)
46
- .map(entry => ({ path: entry.path, type: entry.type }));
47
- return {
48
- ref,
49
- generation,
50
- files,
51
- truncated: entries.length > FILE_SEARCH_ABSOLUTE_MAX,
52
- };
53
- }
54
-
55
- export function buildRgArgs(query: string, max: number, paths: string[], regex = false, omitDirNames: string[] = []): string[] {
56
- const safePaths = paths.length ? paths : ['.'];
57
- const omitGlobs = omitDirNames.flatMap(name => ['--glob', `!${name}/**`, '--glob', `!**/${name}/**`]);
58
- const args = [
59
- 'rg',
60
- '--no-config',
61
- '--line-number',
62
- '--column',
63
- '--no-heading',
64
- '--color',
65
- 'never',
66
- '--smart-case',
67
- '--max-count',
68
- String(max),
69
- '--max-filesize',
70
- '2M',
71
- ...omitGlobs,
72
- '-e',
73
- query,
74
- '--',
75
- ...safePaths,
76
- ];
77
- if (!regex) args.splice(8, 0, '--fixed-strings');
78
- return args;
79
- }
80
-
81
- export function parseRgOutput(stdout: string, max: number, omitDirNames: string[] = []): GrepMatch[] {
82
- const matches: GrepMatch[] = [];
83
- for (const line of stdout.split('\n')) {
84
- if (!line || matches.length >= max) continue;
85
- const parsed = /^(.*):(\d+):(\d+):(.*)$/.exec(line);
86
- if (!parsed) continue;
87
- const path = parsed[1];
88
- const lineNo = Number(parsed[2]);
89
- const column = Number(parsed[3]);
90
- const preview = parsed[4];
91
- if (!path || !lineNo || !column || isSkippableSearchPath(path, omitDirNames)) continue;
92
- matches.push({ path, line: lineNo, column, preview: preview.slice(0, 500) });
93
- }
94
- return matches;
95
- }
96
-
97
- export function parseGitGrepOutput(stdout: string, ref: string, max: number, omitDirNames: string[] = []): GrepMatch[] {
98
- const prefix = ref + ':';
99
- const normalized = stdout
100
- .split('\n')
101
- .map(line => line.startsWith(prefix) ? line.slice(prefix.length) : line)
102
- .join('\n');
103
- return parseRgOutput(normalized, max, omitDirNames);
104
- }
package/web-src/types.ts DELETED
@@ -1,142 +0,0 @@
1
- import type { GdpExpandLogic } from './expand-logic';
2
-
3
- export type FileMeta = {
4
- order?: number;
5
- key?: string;
6
- path: string;
7
- old_path?: string;
8
- display_path?: string;
9
- status?: string;
10
- additions?: number;
11
- deletions?: number;
12
- binary?: boolean;
13
- media_kind?: string | null;
14
- size_class?: string;
15
- force_layout?: string;
16
- highlight?: boolean;
17
- load_url: string;
18
- preview_url?: string | null;
19
- estimated_height_px?: number;
20
- untracked?: boolean;
21
- };
22
-
23
- export type DiffMeta = {
24
- files: FileMeta[];
25
- totals?: {
26
- files: number;
27
- additions: number;
28
- deletions: number;
29
- };
30
- range?: string;
31
- branch?: string;
32
- project?: string;
33
- generation?: number;
34
- };
35
-
36
- export type RepoTreeEntry = {
37
- name: string;
38
- path: string;
39
- type: 'tree' | 'blob' | 'commit';
40
- children_omitted?: true;
41
- children_omitted_reason?: 'heavy' | 'internal' | 'truncated';
42
- };
43
-
44
- export type RepoTreeResponse = {
45
- ref: string;
46
- path: string;
47
- project: string;
48
- branch?: string;
49
- upload_enabled?: boolean;
50
- entries: RepoTreeEntry[];
51
- readme?: {
52
- path: string;
53
- text: string;
54
- } | null;
55
- };
56
-
57
- export type SettingsResponse = {
58
- project: string;
59
- scope: {
60
- omit_dirs_effective: string[];
61
- omit_dirs_built_in: string[];
62
- max_entries: number;
63
- };
64
- };
65
-
66
- export type FileSearchListResponse = {
67
- ref: string;
68
- generation: number;
69
- files: {
70
- path: string;
71
- type: 'blob' | 'commit';
72
- }[];
73
- truncated: boolean;
74
- };
75
-
76
- export type GrepMatch = {
77
- path: string;
78
- line: number;
79
- column: number;
80
- preview: string;
81
- };
82
-
83
- export type GrepResponse = {
84
- ref: string;
85
- engine: 'rg' | 'git' | 'fallback';
86
- truncated: boolean;
87
- matches: GrepMatch[];
88
- };
89
-
90
- export type FileDiffResponse = {
91
- path: string;
92
- old_path?: string;
93
- status?: string;
94
- mode?: string;
95
- diff: string;
96
- hunk_count?: number;
97
- rendered_hunk_count?: number;
98
- truncated?: boolean;
99
- binary?: boolean;
100
- generation?: number;
101
- };
102
-
103
- export type FileRangeResponse = {
104
- path: string;
105
- ref: string;
106
- start: number;
107
- end: number;
108
- lines: string[];
109
- /**
110
- * When complete is true, total is the file's total line count.
111
- * When complete is false, total is only the highest line number the server
112
- * had to scan to prove more lines exist.
113
- */
114
- total: number;
115
- complete?: boolean;
116
- generation?: number;
117
- };
118
-
119
- export type DiffCardElement = HTMLElement & {
120
- _diffData?: FileDiffResponse | null;
121
- _file?: FileMeta | null;
122
- };
123
-
124
- export type RefResponse = {
125
- branches?: string[];
126
- tags?: string[];
127
- commits?: string[];
128
- current?: string;
129
- };
130
-
131
- declare global {
132
- interface Window {
133
- Diff2HtmlUI: any;
134
- hljs: any;
135
- GdpExpandLogic: typeof GdpExpandLogic;
136
- _lastMeta?: DiffMeta;
137
- __gdpScrollSpy?: EventListener;
138
- __gdpSidebarTouchedAt?: number;
139
- }
140
-
141
- const Diff2HtmlUI: any;
142
- }