github-search-cli 9.7.1 → 9.8.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.
@@ -1,211 +0,0 @@
1
- /**
2
- * Minimal markdown-to-terminal-text renderer.
3
- *
4
- * Renders headings, code blocks, lists, tables, blockquotes, horizontal rules,
5
- * bold/italic, and links into a plain-text representation suitable for a
6
- * TextRenderable in the TUI.
7
- */
8
-
9
- interface RenderOptions {
10
- /** Terminal width for wrapping and table layout. Default: 80. */
11
- width?: number;
12
- }
13
-
14
- /** Render markdown text to terminal-friendly plain text. */
15
- export function renderMarkdown(md: string, opts: RenderOptions = {}): string {
16
- const width = opts.width ?? 80;
17
- const lines = md.split("\n");
18
- const out: string[] = [];
19
- let inCodeBlock = false;
20
- let codeLang = "";
21
- let codeLines: string[] = [];
22
- let inTable = false;
23
- let tableRows: string[][] = [];
24
-
25
- function flushCode() {
26
- if (codeLines.length === 0) return;
27
- const maxLen = Math.max(...codeLines.map((l) => l.length), codeLang.length);
28
- const barW = Math.min(maxLen + 4, width - 2);
29
- out.push(`┌${"─".repeat(barW)}┐`);
30
- if (codeLang) out.push(`│ ${codeLang.padEnd(barW - 1)}│`);
31
- for (const cl of codeLines) {
32
- out.push(`│ ${cl.padEnd(barW - 1)}│`);
33
- }
34
- out.push(`└${"─".repeat(barW)}┘`);
35
- out.push("");
36
- codeLines = [];
37
- codeLang = "";
38
- }
39
-
40
- function flushTable() {
41
- if (tableRows.length === 0) return;
42
- const dataRows = tableRows.filter(
43
- (r) => !r.every((c) => /^[\s\-:|]+$/.test(c)),
44
- );
45
- if (dataRows.length === 0) {
46
- tableRows = [];
47
- return;
48
- }
49
- const colCount = Math.max(...dataRows.map((r) => r.length));
50
- const colW: number[] = [];
51
- for (let ci = 0; ci < colCount; ci++) {
52
- colW.push(Math.max(3, ...dataRows.map((r) => (r[ci] ?? "").length)));
53
- }
54
- const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
55
- if (totalW > width) {
56
- for (const row of dataRows) {
57
- out.push(` ${row.map((c) => c.trim()).join(" │ ")}`);
58
- }
59
- } else {
60
- const sep = `┌${colW.map((w) => "─".repeat(w + 2)).join("┬")}┐`;
61
- const mid = `├${colW.map((w) => "─".repeat(w + 2)).join("┼")}┤`;
62
- const end = `└${colW.map((w) => "─".repeat(w + 2)).join("┴")}┘`;
63
- out.push(sep);
64
- dataRows.forEach((row, i) => {
65
- const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
66
- out.push(`│ ${cells.map((c) => ` ${c} `).join("│")}│`);
67
- if (i === 0) out.push(mid);
68
- });
69
- out.push(end);
70
- }
71
- out.push("");
72
- tableRows = [];
73
- }
74
-
75
- function renderInline(line: string): string {
76
- // Bold
77
- line = line.replace(/\*\*(.+?)\*\*/g, "$1");
78
- // Italic (no asterisks)
79
- line = line.replace(/\*([^*]+?)\*/g, "$1");
80
- // Inline code
81
- line = line.replace(/`([^`]+)`/g, "⟨$1⟩");
82
- // Links [text](url)
83
- line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
84
- // Images
85
- line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, "🖼 $1");
86
- return line;
87
- }
88
-
89
- function wrapLine(line: string, maxW: number): string[] {
90
- if (line.length <= maxW) return [line];
91
- const parts: string[] = [];
92
- let remaining = line;
93
- while (remaining.length > maxW) {
94
- let breakAt = maxW;
95
- const spaceIdx = remaining.lastIndexOf(" ", maxW);
96
- if (spaceIdx > maxW / 2) breakAt = spaceIdx;
97
- parts.push(remaining.slice(0, breakAt));
98
- remaining = remaining.slice(breakAt).replace(/^ /, "");
99
- }
100
- if (remaining) parts.push(remaining);
101
- return parts;
102
- }
103
-
104
- for (let i = 0; i < lines.length; i++) {
105
- const line = lines[i];
106
-
107
- // Code block boundaries
108
- if (/^```/.test(line)) {
109
- if (inCodeBlock) {
110
- flushCode();
111
- inCodeBlock = false;
112
- } else {
113
- inCodeBlock = true;
114
- codeLang = line.slice(3).trim();
115
- }
116
- continue;
117
- }
118
- if (inCodeBlock) {
119
- codeLines.push(line);
120
- continue;
121
- }
122
-
123
- // Table row
124
- if (line.includes("|")) {
125
- const cells = line
126
- .split("|")
127
- .map((c) => c.trim())
128
- .filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
129
- if (cells.length >= 2) {
130
- inTable = true;
131
- tableRows.push(cells);
132
- continue;
133
- }
134
- } else if (inTable) {
135
- flushTable();
136
- inTable = false;
137
- }
138
-
139
- // Horizontal rule
140
- if (/^(\s*[-*_]){3,}\s*$/.test(line)) {
141
- out.push("─".repeat(Math.min(width, 40)));
142
- out.push("");
143
- continue;
144
- }
145
-
146
- // Headings
147
- const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
148
- if (headingMatch) {
149
- const level = headingMatch[1].length;
150
- const text = renderInline(headingMatch[2]);
151
- const prefix = "█ ".repeat(level);
152
- out.push("");
153
- out.push(prefix + text);
154
- out.push("");
155
- continue;
156
- }
157
-
158
- // Blockquote
159
- const bqMatch = line.match(/^>\s?(.*)/);
160
- if (bqMatch) {
161
- const inner = renderInline(bqMatch[1]);
162
- for (const wl of wrapLine(inner, width - 4)) {
163
- out.push(`│ ${wl}`);
164
- }
165
- continue;
166
- }
167
-
168
- // Unordered list
169
- const ulMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
170
- if (ulMatch) {
171
- const indent = ulMatch[1].length;
172
- const depth = Math.floor(indent / 2);
173
- const prefix = `${" ".repeat(depth)}• `;
174
- const inner = renderInline(ulMatch[2]);
175
- for (const wl of wrapLine(inner, width - prefix.length)) {
176
- out.push(prefix + wl);
177
- }
178
- continue;
179
- }
180
-
181
- // Ordered list
182
- const olMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
183
- if (olMatch) {
184
- const indent = olMatch[1].length;
185
- const depth = Math.floor(indent / 2);
186
- const prefix = `${" ".repeat(depth)}◦ `;
187
- const inner = renderInline(olMatch[2]);
188
- for (const wl of wrapLine(inner, width - prefix.length)) {
189
- out.push(prefix + wl);
190
- }
191
- continue;
192
- }
193
-
194
- // Empty line
195
- if (line.trim() === "") {
196
- out.push("");
197
- continue;
198
- }
199
-
200
- // Paragraph
201
- const rendered = renderInline(line);
202
- for (const wl of wrapLine(rendered, width)) {
203
- out.push(wl);
204
- }
205
- }
206
-
207
- if (inCodeBlock) flushCode();
208
- if (inTable) flushTable();
209
-
210
- return out.join("\n");
211
- }