@fresh-editor/fresh-editor 0.2.22 → 0.2.24
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/CHANGELOG.md +97 -0
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +521 -381
- package/plugins/audit_mode.ts +3211 -567
- package/plugins/config-schema.json +153 -1
- package/plugins/git_blame.ts +1 -6
- package/plugins/git_explorer.ts +7 -7
- package/plugins/git_log.ts +616 -1025
- package/plugins/lib/fresh.d.ts +94 -4
- package/plugins/lib/git_history.ts +596 -0
- package/plugins/markdown_compose.ts +183 -7
- package/plugins/pkg.ts +151 -397
- package/plugins/search_replace.i18n.json +42 -14
- package/plugins/search_replace.ts +146 -96
- package/plugins/theme_editor.i18n.json +182 -14
- package/plugins/theme_editor.ts +192 -85
- package/plugins/vi_mode.ts +8 -3
|
@@ -37,6 +37,11 @@ function setGlobalComposeEnabled(value: boolean): void {
|
|
|
37
37
|
interface TableWidthInfo {
|
|
38
38
|
maxW: number[];
|
|
39
39
|
allocated: number[];
|
|
40
|
+
// True iff this row is the markdown source separator (`|---|---|---|`) — the
|
|
41
|
+
// border code uses this to avoid drawing a duplicate `├─┼─┤` next to it.
|
|
42
|
+
// Optional for backwards-compat with persisted view states from older
|
|
43
|
+
// sessions.
|
|
44
|
+
isSourceSep?: boolean;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
// Helper: check whether the active split has compose mode for this buffer
|
|
@@ -90,6 +95,161 @@ const HTML_ENTITY_MAP: Record<string, string> = {
|
|
|
90
95
|
laquo: "\u00AB", raquo: "\u00BB", ensp: "\u2002", emsp: "\u2003", thinsp: "\u2009",
|
|
91
96
|
};
|
|
92
97
|
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Table border virtual lines (top/bottom + inter-row separators)
|
|
100
|
+
// =============================================================================
|
|
101
|
+
//
|
|
102
|
+
// Markdown tables source-encode only an underline-style separator between the
|
|
103
|
+
// header and the first data row. In compose mode we already conceal the
|
|
104
|
+
// pipe characters into Unicode box-drawing (`│`, `├`, `┼`, `┤`). This module
|
|
105
|
+
// adds the *missing* visual frame: a `┌─┬─┐` top border above the header,
|
|
106
|
+
// `├─┼─┤` separators between consecutive data rows (so each row reads as a
|
|
107
|
+
// distinct cell), and a `└─┴─┘` bottom border below the last row.
|
|
108
|
+
//
|
|
109
|
+
// Implementation:
|
|
110
|
+
//
|
|
111
|
+
// * Borders are virtual lines (no source bytes), keyed per-line via a
|
|
112
|
+
// unique namespace `md-tb-${lineNumber}`. The namespace lets us
|
|
113
|
+
// clear+rebuild borders for one row without disturbing other tables.
|
|
114
|
+
// * "First/last/source-separator" classification is derived from the
|
|
115
|
+
// cached widthMap (a row is "known" iff it has a TableWidthInfo entry).
|
|
116
|
+
// This is cheap and stable across scrolls because widthMap accumulates.
|
|
117
|
+
// * Border column widths come from the same `allocated` widths used by
|
|
118
|
+
// processLineConceals, so the borders line up exactly with the cell
|
|
119
|
+
// conceals.
|
|
120
|
+
|
|
121
|
+
/** Build a horizontal table border line of the given style for a row. */
|
|
122
|
+
function buildTableBorderLine(
|
|
123
|
+
allocated: number[],
|
|
124
|
+
left: string,
|
|
125
|
+
mid: string,
|
|
126
|
+
right: string,
|
|
127
|
+
): string {
|
|
128
|
+
// Each cell render is `│ <text padded to allocated[i] - 2> │` (2 chars of
|
|
129
|
+
// inside padding). The matching border slot must therefore be
|
|
130
|
+
// `allocated[i]` wide of `─` characters between the corner/junction marks.
|
|
131
|
+
const parts: string[] = [];
|
|
132
|
+
for (let i = 0; i < allocated.length; i++) {
|
|
133
|
+
const fill = "─".repeat(Math.max(1, allocated[i]));
|
|
134
|
+
parts.push(fill);
|
|
135
|
+
}
|
|
136
|
+
return left + parts.join(mid) + right;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** True if `lineContent` looks like a markdown table separator row. */
|
|
140
|
+
function isTableSeparatorContent(lineContent: string): boolean {
|
|
141
|
+
return /^\|[-:\s|]+\|$/.test(lineContent.trim());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Re-emit the table border virtual lines for the given table-row group.
|
|
145
|
+
*
|
|
146
|
+
* Detects the group's first/last visible rows by consulting `widthMap`
|
|
147
|
+
* (which is updated by `processTableAlignment` before this runs). A row at
|
|
148
|
+
* `lineNumber - 1` or `lineNumber + 1` that is *not* in `widthMap` is treated
|
|
149
|
+
* as the boundary of the table's visible extent.
|
|
150
|
+
*/
|
|
151
|
+
function processTableBorders(
|
|
152
|
+
bufferId: number,
|
|
153
|
+
lines: Array<{
|
|
154
|
+
line_number: number;
|
|
155
|
+
byte_start: number;
|
|
156
|
+
byte_end: number;
|
|
157
|
+
content: string;
|
|
158
|
+
}>,
|
|
159
|
+
widthMap: Map<number, TableWidthInfo>,
|
|
160
|
+
): void {
|
|
161
|
+
// Use theme keys (resolved at render time so the borders follow theme
|
|
162
|
+
// changes — same pattern as addOverlay's fg/bg options).
|
|
163
|
+
//
|
|
164
|
+
// * fg → editor.fg (the default document foreground, matching the
|
|
165
|
+
// concealed `│` / `─` glyphs inside row text so the virtual
|
|
166
|
+
// `┌─┬─┐` / `├─┼─┤` / `└─┴─┘` frame doesn't create a visible seam
|
|
167
|
+
// where it meets the in-text borders)
|
|
168
|
+
// * bg → editor.bg (matches the document background so the borders
|
|
169
|
+
// blend in rather than carving an opaque slab through the page)
|
|
170
|
+
const borderOptions = { fg: "editor.fg", bg: "editor.bg" };
|
|
171
|
+
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
const ns = `md-tb-${line.line_number}`;
|
|
174
|
+
// Always start by clearing this row's previous borders (handles
|
|
175
|
+
// edits that removed/widened the row, scrolls that change the
|
|
176
|
+
// first/last classification, etc.).
|
|
177
|
+
editor.clearVirtualTextNamespace(bufferId, ns);
|
|
178
|
+
|
|
179
|
+
const trimmed = line.content.trim();
|
|
180
|
+
const isTableRow = trimmed.startsWith("|") || trimmed.endsWith("|");
|
|
181
|
+
if (!isTableRow) continue;
|
|
182
|
+
|
|
183
|
+
const widthInfo = widthMap.get(line.line_number);
|
|
184
|
+
if (!widthInfo || widthInfo.allocated.length === 0) continue;
|
|
185
|
+
|
|
186
|
+
const allocated = widthInfo.allocated;
|
|
187
|
+
// Prefer the cached flag (set by processTableAlignment from the source
|
|
188
|
+
// text of this exact row); fall back to a regex check in case this row
|
|
189
|
+
// was loaded from a persisted view state without the flag.
|
|
190
|
+
const isSourceSep = widthInfo.isSourceSep === true
|
|
191
|
+
|| isTableSeparatorContent(line.content);
|
|
192
|
+
|
|
193
|
+
const prevIsTable = widthMap.has(line.line_number - 1);
|
|
194
|
+
const nextIsTable = widthMap.has(line.line_number + 1);
|
|
195
|
+
|
|
196
|
+
// Top border: only above the very first known row of the table.
|
|
197
|
+
// ┌─┬─┐ — opens the frame above the header.
|
|
198
|
+
if (!prevIsTable) {
|
|
199
|
+
editor.addVirtualLine(
|
|
200
|
+
bufferId,
|
|
201
|
+
line.byte_start,
|
|
202
|
+
buildTableBorderLine(allocated, "┌", "┬", "┐"),
|
|
203
|
+
borderOptions,
|
|
204
|
+
true, // above
|
|
205
|
+
ns,
|
|
206
|
+
0,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Inter-row separator: between consecutive *data* rows.
|
|
211
|
+
//
|
|
212
|
+
// Skip if either side is the source separator row (`|---|---|---|`)
|
|
213
|
+
// because the source already provides `├─┼─┤` there via conceals —
|
|
214
|
+
// adding another above/below would draw two adjacent separator lines.
|
|
215
|
+
//
|
|
216
|
+
// Drawn ABOVE the current row when its predecessor is also a (non-
|
|
217
|
+
// source-separator) table row, so each row owns the separator that
|
|
218
|
+
// appears above it.
|
|
219
|
+
const prevInfo = widthMap.get(line.line_number - 1);
|
|
220
|
+
const prevIsSourceSep = prevInfo?.isSourceSep === true;
|
|
221
|
+
if (prevIsTable && !isSourceSep && !prevIsSourceSep) {
|
|
222
|
+
editor.addVirtualLine(
|
|
223
|
+
bufferId,
|
|
224
|
+
line.byte_start,
|
|
225
|
+
buildTableBorderLine(allocated, "├", "┼", "┤"),
|
|
226
|
+
borderOptions,
|
|
227
|
+
true, // above
|
|
228
|
+
ns,
|
|
229
|
+
1,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Bottom border: only below the last known row of the table.
|
|
234
|
+
// └─┴─┘ — closes the frame. Anchor at the END of the row's bytes
|
|
235
|
+
// (one before the trailing newline) and place "below".
|
|
236
|
+
if (!nextIsTable) {
|
|
237
|
+
// byte_end points just past the newline; anchor at last byte of
|
|
238
|
+
// the row content so the virtual line renders directly under it.
|
|
239
|
+
const anchor = Math.max(line.byte_start, line.byte_end - 1);
|
|
240
|
+
editor.addVirtualLine(
|
|
241
|
+
bufferId,
|
|
242
|
+
anchor,
|
|
243
|
+
buildTableBorderLine(allocated, "└", "┴", "┘"),
|
|
244
|
+
borderOptions,
|
|
245
|
+
false, // below
|
|
246
|
+
ns,
|
|
247
|
+
0,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
93
253
|
// =============================================================================
|
|
94
254
|
// Block-based parser for hanging indent support
|
|
95
255
|
// =============================================================================
|
|
@@ -1375,18 +1535,25 @@ function processTableAlignment(
|
|
|
1375
1535
|
if (allocGrew(widthMap.get(ln)!)) { needsRefresh = true; break; }
|
|
1376
1536
|
}
|
|
1377
1537
|
|
|
1378
|
-
// Store merged widths for
|
|
1379
|
-
//
|
|
1380
|
-
//
|
|
1381
|
-
|
|
1538
|
+
// Store merged widths for each line in the group. We tag the source
|
|
1539
|
+
// separator row (`|---|---|---|`) so the border renderer can skip
|
|
1540
|
+
// drawing a duplicate `├─┼─┤` adjacent to it (the source separator is
|
|
1541
|
+
// already concealed into one). Each line gets its own info object so
|
|
1542
|
+
// the per-row `isSourceSep` flag is independent.
|
|
1382
1543
|
for (const line of group) {
|
|
1383
|
-
|
|
1544
|
+
const isSep = /^\|[-:\s|]+\|$/.test(line.content.trim());
|
|
1545
|
+
widthMap.set(line.line_number, { maxW: merged, allocated, isSourceSep: isSep });
|
|
1384
1546
|
}
|
|
1547
|
+
// Adjacent cached lines (already-processed neighbours of this group)
|
|
1548
|
+
// need their `allocated` updated but should keep their existing
|
|
1549
|
+
// `isSourceSep` flag — they were classified when they were processed.
|
|
1385
1550
|
for (let ln = firstLine - 1; widthMap.has(ln); ln--) {
|
|
1386
|
-
widthMap.
|
|
1551
|
+
const prev = widthMap.get(ln)!;
|
|
1552
|
+
widthMap.set(ln, { maxW: merged, allocated, isSourceSep: prev.isSourceSep });
|
|
1387
1553
|
}
|
|
1388
1554
|
for (let ln = lastLine + 1; widthMap.has(ln); ln++) {
|
|
1389
|
-
widthMap.
|
|
1555
|
+
const prev = widthMap.get(ln)!;
|
|
1556
|
+
widthMap.set(ln, { maxW: merged, allocated, isSourceSep: prev.isSourceSep });
|
|
1390
1557
|
}
|
|
1391
1558
|
}
|
|
1392
1559
|
|
|
@@ -1426,6 +1593,15 @@ function onMarkdownLinesChanged(data: {
|
|
|
1426
1593
|
processLineSoftBreaks(data.buffer_id, line.content, line.byte_start, line.byte_end, cursors, line.line_number);
|
|
1427
1594
|
}
|
|
1428
1595
|
|
|
1596
|
+
// Add/refresh table border virtual lines (top/bottom + inter-row separators).
|
|
1597
|
+
// Runs AFTER processTableAlignment so the widthMap reflects the latest
|
|
1598
|
+
// allocated widths, and AFTER processLineConceals so the borders we draw
|
|
1599
|
+
// line up with the cell pipes the conceals produce.
|
|
1600
|
+
const widthMapForBorders = getTableWidths(data.buffer_id);
|
|
1601
|
+
if (widthMapForBorders) {
|
|
1602
|
+
processTableBorders(data.buffer_id, data.lines, widthMapForBorders);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1429
1605
|
if (tableWidthsGrew) {
|
|
1430
1606
|
editor.refreshLines(data.buffer_id);
|
|
1431
1607
|
}
|