github-search-cli 9.1.1 → 9.3.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/README.md +25 -10
- package/bin/ghfind.js +3 -3
- package/dist/cli.js +258 -78
- package/package.json +2 -4
- package/scripts/postinstall.js +10 -7
- package/src/cli.ts +28 -1
- package/src/compare.ts +4 -3
- package/src/help.ts +142 -142
- package/src/markdown-render.ts +216 -0
- package/src/package-browser.ts +305 -305
- package/src/package.ts +137 -137
- package/src/search.ts +1 -0
- package/src/trending.ts +31 -5
- package/src/tui.ts +27 -47
- package/vendor/bun.exe +0 -0
package/src/help.ts
CHANGED
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
/** Help screen — structured sections rendered by OpenTUI components. */
|
|
2
|
-
export interface HelpRow {
|
|
3
|
-
keys: string;
|
|
4
|
-
action: string;
|
|
5
|
-
}
|
|
6
|
-
export interface HelpSection {
|
|
7
|
-
title: string;
|
|
8
|
-
/** Theme color key or hex — used for Box title color */
|
|
9
|
-
titleColor: string;
|
|
10
|
-
rows: HelpRow[];
|
|
11
|
-
note?: string;
|
|
12
|
-
}
|
|
13
|
-
export function buildHelpSections(): HelpSection[] {
|
|
14
|
-
return [
|
|
15
|
-
{
|
|
16
|
-
title: " Landing ",
|
|
17
|
-
titleColor: "blue",
|
|
18
|
-
rows: [
|
|
19
|
-
{ keys: "↑ ↓ / j k", action: "Navigate menu cards" },
|
|
20
|
-
{ keys: "Enter", action: "Select mode" },
|
|
21
|
-
{ keys: "? / h", action: "Toggle this help screen" },
|
|
22
|
-
{ keys: "q", action: "Quit" },
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
title: " Navigation ",
|
|
27
|
-
titleColor: "blue",
|
|
28
|
-
rows: [
|
|
29
|
-
{ keys: "↑ ↓ / j k", action: "Move selection" },
|
|
30
|
-
{ keys: "Enter", action: "Open repo in browser" },
|
|
31
|
-
{ keys: "/", action: "Focus search input" },
|
|
32
|
-
{ keys: "Space", action: "Open command menu" },
|
|
33
|
-
{ keys: "? / Ctrl+H", action: "Toggle this help screen" },
|
|
34
|
-
{ keys: "q", action: "Quit" },
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
title: " Main View ",
|
|
39
|
-
titleColor: "green",
|
|
40
|
-
rows: [
|
|
41
|
-
{ keys: "Tab", action: "Auto-complete qualifier" },
|
|
42
|
-
{ keys: "PageDown", action: "Next page of results" },
|
|
43
|
-
{ keys: "PageUp", action: "Scroll to top of results" },
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
title: " Trending Mode ",
|
|
48
|
-
titleColor: "yellow",
|
|
49
|
-
rows: [
|
|
50
|
-
{ keys: "1-5", action: "Switch tab: Today → Week → Month → …" },
|
|
51
|
-
{ keys: "← → / h l", action: "Navigate tabs" },
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
title: " Packages Mode ",
|
|
56
|
-
titleColor: "cyan",
|
|
57
|
-
rows: [
|
|
58
|
-
{ keys: "/", action: "Search npm packages" },
|
|
59
|
-
{ keys: "↑ ↓ / j k", action: "Move selection" },
|
|
60
|
-
{ keys: "Enter", action: "View package details" },
|
|
61
|
-
{ keys: "Space", action: "Open command menu" },
|
|
62
|
-
],
|
|
63
|
-
note: "Search by name, description, or tags. Sort by downloads or score.",
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
title: " Command Menu (Space)",
|
|
67
|
-
titleColor: "purple",
|
|
68
|
-
rows: [
|
|
69
|
-
{ keys: "Sort", action: "Cycle sort: best-match → stars → …" },
|
|
70
|
-
{ keys: "Limit", action: "Cycle limit: 10 → 25 → 50 → 100" },
|
|
71
|
-
{ keys: "Refresh", action: "Re-run current search" },
|
|
72
|
-
{ keys: "Graph", action: "Toggle commit activity chart" },
|
|
73
|
-
{ keys: "Readme", action: "Full README viewer" },
|
|
74
|
-
{ keys: "Deep-dive", action: "Languages + contributors + excerpt" },
|
|
75
|
-
{ keys: "Bookmark", action: "Save / unsave selected repo" },
|
|
76
|
-
{ keys: "Compare", action: "Add to / remove from comparison" },
|
|
77
|
-
{ keys: "Trending", action: "Switch to trending browser" },
|
|
78
|
-
{ keys: "Packages", action: "Switch to npm packages" },
|
|
79
|
-
{ keys: "Export", action: "Export results to file" },
|
|
80
|
-
{ keys: "Share", action: "Share repo as text" },
|
|
81
|
-
{ keys: "Topics", action: "Browse GitHub topics" },
|
|
82
|
-
{ keys: "Saved", action: "View saved searches" },
|
|
83
|
-
{ keys: "Notifs", action: "Check GitHub notifications" },
|
|
84
|
-
{ keys: "History", action: "View search history" },
|
|
85
|
-
{ keys: "Help", action: "Show this help screen" },
|
|
86
|
-
],
|
|
87
|
-
note: "Navigate menu with ↑ ↓, select with Enter, back with ← / Esc",
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
title: " Overlay Keys ",
|
|
91
|
-
titleColor: "teal",
|
|
92
|
-
rows: [
|
|
93
|
-
{ keys: "Esc / q", action: "Close any overlay" },
|
|
94
|
-
{ keys: "Enter", action: "Confirm selection / run action" },
|
|
95
|
-
{ keys: "d", action: "Delete entry (history, bookmarks, saved)" },
|
|
96
|
-
{ keys: "↑ ↓ / j k", action: "Navigate overlay list" },
|
|
97
|
-
{ keys: "← → / h l", action: "Navigate tabs / panels" },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
title: " Search Syntax ",
|
|
102
|
-
titleColor: "teal",
|
|
103
|
-
rows: [
|
|
104
|
-
{ keys: "keywords", action: "Free-text search" },
|
|
105
|
-
{ keys: "key:value", action: "Qualifier filter" },
|
|
106
|
-
{ keys: "-key:value", action: "Negated qualifier" },
|
|
107
|
-
{ keys: '"phrase"', action: "Exact phrase match" },
|
|
108
|
-
{ keys: "language:TypeScript", action: "Filter by language" },
|
|
109
|
-
{ keys: "stars:>1000", action: "Minimum stars" },
|
|
110
|
-
{ keys: "fork:true", action: "Include forks" },
|
|
111
|
-
{ keys: "user:octocat", action: "User's repos" },
|
|
112
|
-
{ keys: "org:facebook", action: "Organization repos" },
|
|
113
|
-
{ keys: "repo:facebook/react", action: "Specific repo" },
|
|
114
|
-
{ keys: "topic:react", action: "Filter by topic" },
|
|
115
|
-
{ keys: "license:mit", action: "License type" },
|
|
116
|
-
{ keys: "pushed:>2024-01-01", action: "Last push date" },
|
|
117
|
-
{ keys: "archived:false", action: "Exclude archived" },
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
title: " Utility Keys ",
|
|
122
|
-
titleColor: "muted",
|
|
123
|
-
rows: [
|
|
124
|
-
{ keys: "t", action: "Cycle theme (light / dark / solarized)" },
|
|
125
|
-
{ keys: "Ctrl+X", action: "Copy repo URL to clipboard" },
|
|
126
|
-
{ keys: "Ctrl+C", action: "Copy search query to clipboard" },
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
title: " Rate Limits ",
|
|
131
|
-
titleColor: "orange",
|
|
132
|
-
rows: [
|
|
133
|
-
{ keys: "No token", action: "10 req / min" },
|
|
134
|
-
{ keys: "GITHUB_TOKEN", action: "30 req / min (or your plan limit)" },
|
|
135
|
-
],
|
|
136
|
-
note: "Set GITHUB_TOKEN in your environment or add to config.",
|
|
137
|
-
},
|
|
138
|
-
];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Column at which the action description starts (right-aligns keys). */
|
|
142
|
-
export const HELP_KEYS_COLUMN = 24;
|
|
1
|
+
/** Help screen — structured sections rendered by OpenTUI components. */
|
|
2
|
+
export interface HelpRow {
|
|
3
|
+
keys: string;
|
|
4
|
+
action: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HelpSection {
|
|
7
|
+
title: string;
|
|
8
|
+
/** Theme color key or hex — used for Box title color */
|
|
9
|
+
titleColor: string;
|
|
10
|
+
rows: HelpRow[];
|
|
11
|
+
note?: string;
|
|
12
|
+
}
|
|
13
|
+
export function buildHelpSections(): HelpSection[] {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
title: " Landing ",
|
|
17
|
+
titleColor: "blue",
|
|
18
|
+
rows: [
|
|
19
|
+
{ keys: "↑ ↓ / j k", action: "Navigate menu cards" },
|
|
20
|
+
{ keys: "Enter", action: "Select mode" },
|
|
21
|
+
{ keys: "? / h", action: "Toggle this help screen" },
|
|
22
|
+
{ keys: "q", action: "Quit" },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: " Navigation ",
|
|
27
|
+
titleColor: "blue",
|
|
28
|
+
rows: [
|
|
29
|
+
{ keys: "↑ ↓ / j k", action: "Move selection" },
|
|
30
|
+
{ keys: "Enter", action: "Open repo in browser" },
|
|
31
|
+
{ keys: "/", action: "Focus search input" },
|
|
32
|
+
{ keys: "Space", action: "Open command menu" },
|
|
33
|
+
{ keys: "? / Ctrl+H", action: "Toggle this help screen" },
|
|
34
|
+
{ keys: "q", action: "Quit" },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
title: " Main View ",
|
|
39
|
+
titleColor: "green",
|
|
40
|
+
rows: [
|
|
41
|
+
{ keys: "Tab", action: "Auto-complete qualifier" },
|
|
42
|
+
{ keys: "PageDown", action: "Next page of results" },
|
|
43
|
+
{ keys: "PageUp", action: "Scroll to top of results" },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: " Trending Mode ",
|
|
48
|
+
titleColor: "yellow",
|
|
49
|
+
rows: [
|
|
50
|
+
{ keys: "1-5", action: "Switch tab: Today → Week → Month → …" },
|
|
51
|
+
{ keys: "← → / h l", action: "Navigate tabs" },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: " Packages Mode ",
|
|
56
|
+
titleColor: "cyan",
|
|
57
|
+
rows: [
|
|
58
|
+
{ keys: "/", action: "Search npm packages" },
|
|
59
|
+
{ keys: "↑ ↓ / j k", action: "Move selection" },
|
|
60
|
+
{ keys: "Enter", action: "View package details" },
|
|
61
|
+
{ keys: "Space", action: "Open command menu" },
|
|
62
|
+
],
|
|
63
|
+
note: "Search by name, description, or tags. Sort by downloads or score.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
title: " Command Menu (Space)",
|
|
67
|
+
titleColor: "purple",
|
|
68
|
+
rows: [
|
|
69
|
+
{ keys: "Sort", action: "Cycle sort: best-match → stars → …" },
|
|
70
|
+
{ keys: "Limit", action: "Cycle limit: 10 → 25 → 50 → 100" },
|
|
71
|
+
{ keys: "Refresh", action: "Re-run current search" },
|
|
72
|
+
{ keys: "Graph", action: "Toggle commit activity chart" },
|
|
73
|
+
{ keys: "Readme", action: "Full README viewer" },
|
|
74
|
+
{ keys: "Deep-dive", action: "Languages + contributors + excerpt" },
|
|
75
|
+
{ keys: "Bookmark", action: "Save / unsave selected repo" },
|
|
76
|
+
{ keys: "Compare", action: "Add to / remove from comparison" },
|
|
77
|
+
{ keys: "Trending", action: "Switch to trending browser" },
|
|
78
|
+
{ keys: "Packages", action: "Switch to npm packages" },
|
|
79
|
+
{ keys: "Export", action: "Export results to file" },
|
|
80
|
+
{ keys: "Share", action: "Share repo as text" },
|
|
81
|
+
{ keys: "Topics", action: "Browse GitHub topics" },
|
|
82
|
+
{ keys: "Saved", action: "View saved searches" },
|
|
83
|
+
{ keys: "Notifs", action: "Check GitHub notifications" },
|
|
84
|
+
{ keys: "History", action: "View search history" },
|
|
85
|
+
{ keys: "Help", action: "Show this help screen" },
|
|
86
|
+
],
|
|
87
|
+
note: "Navigate menu with ↑ ↓, select with Enter, back with ← / Esc",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
title: " Overlay Keys ",
|
|
91
|
+
titleColor: "teal",
|
|
92
|
+
rows: [
|
|
93
|
+
{ keys: "Esc / q", action: "Close any overlay" },
|
|
94
|
+
{ keys: "Enter", action: "Confirm selection / run action" },
|
|
95
|
+
{ keys: "d", action: "Delete entry (history, bookmarks, saved)" },
|
|
96
|
+
{ keys: "↑ ↓ / j k", action: "Navigate overlay list" },
|
|
97
|
+
{ keys: "← → / h l", action: "Navigate tabs / panels" },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: " Search Syntax ",
|
|
102
|
+
titleColor: "teal",
|
|
103
|
+
rows: [
|
|
104
|
+
{ keys: "keywords", action: "Free-text search" },
|
|
105
|
+
{ keys: "key:value", action: "Qualifier filter" },
|
|
106
|
+
{ keys: "-key:value", action: "Negated qualifier" },
|
|
107
|
+
{ keys: '"phrase"', action: "Exact phrase match" },
|
|
108
|
+
{ keys: "language:TypeScript", action: "Filter by language" },
|
|
109
|
+
{ keys: "stars:>1000", action: "Minimum stars" },
|
|
110
|
+
{ keys: "fork:true", action: "Include forks" },
|
|
111
|
+
{ keys: "user:octocat", action: "User's repos" },
|
|
112
|
+
{ keys: "org:facebook", action: "Organization repos" },
|
|
113
|
+
{ keys: "repo:facebook/react", action: "Specific repo" },
|
|
114
|
+
{ keys: "topic:react", action: "Filter by topic" },
|
|
115
|
+
{ keys: "license:mit", action: "License type" },
|
|
116
|
+
{ keys: "pushed:>2024-01-01", action: "Last push date" },
|
|
117
|
+
{ keys: "archived:false", action: "Exclude archived" },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: " Utility Keys ",
|
|
122
|
+
titleColor: "muted",
|
|
123
|
+
rows: [
|
|
124
|
+
{ keys: "t", action: "Cycle theme (light / dark / solarized)" },
|
|
125
|
+
{ keys: "Ctrl+X", action: "Copy repo URL to clipboard" },
|
|
126
|
+
{ keys: "Ctrl+C", action: "Copy search query to clipboard" },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
title: " Rate Limits ",
|
|
131
|
+
titleColor: "orange",
|
|
132
|
+
rows: [
|
|
133
|
+
{ keys: "No token", action: "10 req / min" },
|
|
134
|
+
{ keys: "GITHUB_TOKEN", action: "30 req / min (or your plan limit)" },
|
|
135
|
+
],
|
|
136
|
+
note: "Set GITHUB_TOKEN in your environment or add to config.",
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Column at which the action description starts (right-aligns keys). */
|
|
142
|
+
export const HELP_KEYS_COLUMN = 24;
|
|
@@ -0,0 +1,216 @@
|
|
|
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(
|
|
53
|
+
Math.max(
|
|
54
|
+
3,
|
|
55
|
+
...dataRows.map((r) => (r[ci] ?? "").length),
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
|
|
60
|
+
if (totalW > width) {
|
|
61
|
+
for (const row of dataRows) {
|
|
62
|
+
out.push(" " + row.map((c) => c.trim()).join(" │ "));
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
const sep = "┌" + colW.map((w) => "─".repeat(w + 2)).join("┬") + "┐";
|
|
66
|
+
const mid = "├" + colW.map((w) => "─".repeat(w + 2)).join("┼") + "┤";
|
|
67
|
+
const end = "└" + colW.map((w) => "─".repeat(w + 2)).join("┴") + "┘";
|
|
68
|
+
out.push(sep);
|
|
69
|
+
dataRows.forEach((row, i) => {
|
|
70
|
+
const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
|
|
71
|
+
out.push("│ " + cells.map((c) => ` ${c} `).join("│") + "│");
|
|
72
|
+
if (i === 0) out.push(mid);
|
|
73
|
+
});
|
|
74
|
+
out.push(end);
|
|
75
|
+
}
|
|
76
|
+
out.push("");
|
|
77
|
+
tableRows = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderInline(line: string): string {
|
|
81
|
+
// Bold
|
|
82
|
+
line = line.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
83
|
+
// Italic (no asterisks)
|
|
84
|
+
line = line.replace(/\*([^*]+?)\*/g, "$1");
|
|
85
|
+
// Inline code
|
|
86
|
+
line = line.replace(/`([^`]+)`/g, "⟨$1⟩");
|
|
87
|
+
// Links [text](url)
|
|
88
|
+
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
89
|
+
// Images
|
|
90
|
+
line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, "🖼 $1");
|
|
91
|
+
return line;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wrapLine(line: string, maxW: number): string[] {
|
|
95
|
+
if (line.length <= maxW) return [line];
|
|
96
|
+
const parts: string[] = [];
|
|
97
|
+
let remaining = line;
|
|
98
|
+
while (remaining.length > maxW) {
|
|
99
|
+
let breakAt = maxW;
|
|
100
|
+
const spaceIdx = remaining.lastIndexOf(" ", maxW);
|
|
101
|
+
if (spaceIdx > maxW / 2) breakAt = spaceIdx;
|
|
102
|
+
parts.push(remaining.slice(0, breakAt));
|
|
103
|
+
remaining = remaining.slice(breakAt).replace(/^ /, "");
|
|
104
|
+
}
|
|
105
|
+
if (remaining) parts.push(remaining);
|
|
106
|
+
return parts;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
110
|
+
let line = lines[i];
|
|
111
|
+
|
|
112
|
+
// Code block boundaries
|
|
113
|
+
if (/^```/.test(line)) {
|
|
114
|
+
if (inCodeBlock) {
|
|
115
|
+
flushCode();
|
|
116
|
+
inCodeBlock = false;
|
|
117
|
+
} else {
|
|
118
|
+
inCodeBlock = true;
|
|
119
|
+
codeLang = line.slice(3).trim();
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (inCodeBlock) {
|
|
124
|
+
codeLines.push(line);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Table row
|
|
129
|
+
if (line.includes("|")) {
|
|
130
|
+
const cells = line
|
|
131
|
+
.split("|")
|
|
132
|
+
.map((c) => c.trim())
|
|
133
|
+
.filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
134
|
+
if (cells.length >= 2) {
|
|
135
|
+
inTable = true;
|
|
136
|
+
tableRows.push(cells);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
} else if (inTable) {
|
|
140
|
+
flushTable();
|
|
141
|
+
inTable = false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Horizontal rule
|
|
145
|
+
if (/^(\s*[-*_]){3,}\s*$/.test(line)) {
|
|
146
|
+
out.push("─".repeat(Math.min(width, 40)));
|
|
147
|
+
out.push("");
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Headings
|
|
152
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
|
|
153
|
+
if (headingMatch) {
|
|
154
|
+
const level = headingMatch[1].length;
|
|
155
|
+
const text = renderInline(headingMatch[2]);
|
|
156
|
+
const prefix = "█ ".repeat(level);
|
|
157
|
+
out.push("");
|
|
158
|
+
out.push(prefix + text);
|
|
159
|
+
out.push("");
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Blockquote
|
|
164
|
+
const bqMatch = line.match(/^>\s?(.*)/);
|
|
165
|
+
if (bqMatch) {
|
|
166
|
+
const inner = renderInline(bqMatch[1]);
|
|
167
|
+
for (const wl of wrapLine(inner, width - 4)) {
|
|
168
|
+
out.push("│ " + wl);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Unordered list
|
|
174
|
+
const ulMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
|
|
175
|
+
if (ulMatch) {
|
|
176
|
+
const indent = ulMatch[1].length;
|
|
177
|
+
const depth = Math.floor(indent / 2);
|
|
178
|
+
const prefix = " ".repeat(depth) + "• ";
|
|
179
|
+
const inner = renderInline(ulMatch[2]);
|
|
180
|
+
for (const wl of wrapLine(inner, width - prefix.length)) {
|
|
181
|
+
out.push(prefix + wl);
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Ordered list
|
|
187
|
+
const olMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
|
|
188
|
+
if (olMatch) {
|
|
189
|
+
const indent = olMatch[1].length;
|
|
190
|
+
const depth = Math.floor(indent / 2);
|
|
191
|
+
const prefix = " ".repeat(depth) + "◦ ";
|
|
192
|
+
const inner = renderInline(olMatch[2]);
|
|
193
|
+
for (const wl of wrapLine(inner, width - prefix.length)) {
|
|
194
|
+
out.push(prefix + wl);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Empty line
|
|
200
|
+
if (line.trim() === "") {
|
|
201
|
+
out.push("");
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Paragraph
|
|
206
|
+
const rendered = renderInline(line);
|
|
207
|
+
for (const wl of wrapLine(rendered, width)) {
|
|
208
|
+
out.push(wl);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (inCodeBlock) flushCode();
|
|
213
|
+
if (inTable) flushTable();
|
|
214
|
+
|
|
215
|
+
return out.join("\n");
|
|
216
|
+
}
|