@fresh-editor/fresh-editor 0.3.5 → 0.3.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.
- package/CHANGELOG.md +147 -0
- package/README.md +9 -2
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +84 -0
- package/plugins/audit_mode.ts +139 -3
- package/plugins/config-schema.json +33 -3
- package/plugins/dashboard.ts +34 -111
- package/plugins/flash.ts +22 -4
- package/plugins/git_blame.ts +10 -6
- package/plugins/git_log.ts +705 -323
- package/plugins/git_statusbar.i18n.json +72 -0
- package/plugins/git_statusbar.ts +133 -0
- package/plugins/goto_with_selection.i18n.json +58 -0
- package/plugins/goto_with_selection.ts +17 -0
- package/plugins/lib/fresh.d.ts +911 -15
- package/plugins/lib/index.ts +34 -0
- package/plugins/lib/widgets.ts +903 -0
- package/plugins/live_diff.ts +442 -32
- package/plugins/merge_conflict.ts +89 -64
- package/plugins/orchestrator.ts +3425 -0
- package/plugins/pkg.ts +235 -54
- package/plugins/rust-lsp.ts +58 -40
- package/plugins/schemas/theme.schema.json +18 -0
- package/plugins/search_replace.i18n.json +140 -28
- package/plugins/search_replace.ts +1335 -515
- package/plugins/tab_actions.i18n.json +212 -0
- package/plugins/tab_actions.ts +76 -0
- package/plugins/theme_editor.i18n.json +112 -0
- package/plugins/theme_editor.ts +30 -5
- package/plugins/tsconfig.json +3 -0
- package/plugins/vi_mode.ts +49 -17
- package/themes/dark.json +1 -0
- package/themes/dracula.json +1 -0
- package/themes/high-contrast.json +1 -0
- package/themes/light.json +1 -0
- package/themes/nord.json +1 -0
- package/themes/nostalgia.json +1 -0
- package/themes/solarized-dark.json +1 -0
- package/themes/terminal.json +4 -0
package/plugins/dashboard.ts
CHANGED
|
@@ -201,11 +201,6 @@ type RegisteredSection = {
|
|
|
201
201
|
let dashboardBufferId: number | null = null;
|
|
202
202
|
let fetchToken = 0; // bumped each open; late fetches from a prior open no-op.
|
|
203
203
|
|
|
204
|
-
// Id of the in-flight slide-in, so we can cancel it when starting a
|
|
205
|
-
// new one (on content change) or when the dashboard is closed
|
|
206
|
-
// mid-slide. Null once the animation settles or is cleared.
|
|
207
|
-
let activeAnimationId: number | null = null;
|
|
208
|
-
|
|
209
204
|
// Hash of all entries at the last paint (post-focus-highlight too —
|
|
210
205
|
// it's what ultimately lands in the virtual buffer). Used to decide
|
|
211
206
|
// whether setVirtualBufferContent needs to run at all: identical
|
|
@@ -213,43 +208,11 @@ let activeAnimationId: number | null = null;
|
|
|
213
208
|
// round-trip entirely.
|
|
214
209
|
let lastPaintedFullKey: string | null = null;
|
|
215
210
|
|
|
216
|
-
// Hash of the entries with the clock stamp stripped. Animations only
|
|
217
|
-
// fire when THIS hash changes, so the 1 Hz clock tick on the top
|
|
218
|
-
// frame updates in place without re-sliding the whole dashboard.
|
|
219
|
-
// Keyboard focus changes don't move this hash either (the hash is
|
|
220
|
-
// taken before the focus overlay is laid on top), so Tab/Shift-Tab
|
|
221
|
-
// pan the highlight without re-animating.
|
|
222
|
-
let lastPaintedStructuralKey: string | null = null;
|
|
223
|
-
|
|
224
211
|
// focusedIndex the last successful setVirtualBufferContent ran with.
|
|
225
|
-
// Paired with the
|
|
226
|
-
//
|
|
212
|
+
// Paired with the key above so we can tell "focus moved but content
|
|
213
|
+
// is the same" and still update VB for the highlight.
|
|
227
214
|
let lastPaintedFocusedIndex = -1;
|
|
228
215
|
|
|
229
|
-
// Matches an HH:MM:SS clock stamp. Anything shaped like that is
|
|
230
|
-
// stripped from the structural hash so clock ticks don't animate.
|
|
231
|
-
// The frame renderer is the only dashboard author that emits such a
|
|
232
|
-
// string; if a third-party section happens to show a value in the
|
|
233
|
-
// same shape, the worst case is "we don't re-animate when that
|
|
234
|
-
// value changes" — acceptable noise floor.
|
|
235
|
-
const CLOCK_RE = /\d\d:\d\d:\d\d/g;
|
|
236
|
-
|
|
237
|
-
// Edge the slide-in enters from. Maps 1:1 to the plugin API's `from`
|
|
238
|
-
// field and is resolved from config (plugins.dashboard.slide_from) on
|
|
239
|
-
// each paint() so hot-reload of the setting Just Works. Defaults to
|
|
240
|
-
// "right" (new content pushes in from the right, old exits left).
|
|
241
|
-
type SlideFrom = "top" | "bottom" | "left" | "right";
|
|
242
|
-
function resolveSlideFrom(): SlideFrom {
|
|
243
|
-
const config = editor.getConfig() as Record<string, unknown> | null;
|
|
244
|
-
const plugins = config?.plugins as Record<string, unknown> | undefined;
|
|
245
|
-
const dashCfg = plugins?.dashboard as Record<string, unknown> | undefined;
|
|
246
|
-
const raw = dashCfg?.slide_from;
|
|
247
|
-
if (raw === "top" || raw === "bottom" || raw === "left" || raw === "right") {
|
|
248
|
-
return raw;
|
|
249
|
-
}
|
|
250
|
-
return "right";
|
|
251
|
-
}
|
|
252
|
-
|
|
253
216
|
// Registered sections, in render order. Built-ins are registered at
|
|
254
217
|
// plugin load (see the bottom of this file); third-party plugins
|
|
255
218
|
// append via the exported `registerSection` API.
|
|
@@ -735,6 +698,13 @@ function paint(dims?: { width: number; height: number }) {
|
|
|
735
698
|
const entries: TextPropertyEntry[] = [];
|
|
736
699
|
for (let i = 0; i < topPad; i++) entries.push({ text: "\n" });
|
|
737
700
|
for (const e of drawToEntries(drawn)) entries.push(e);
|
|
701
|
+
// Pad below the frame so the buffer covers the full viewport height.
|
|
702
|
+
// Without this, rows past the last frame line render with
|
|
703
|
+
// `editor.after_eof_bg` (a deliberate shade off from `editor.bg` to mark
|
|
704
|
+
// end-of-file in code buffers) and show up as a different-colored strip
|
|
705
|
+
// at the bottom of the dashboard.
|
|
706
|
+
const bottomPad = Math.max(0, height - topPad - frameHeight);
|
|
707
|
+
for (let i = 0; i < bottomPad; i++) entries.push({ text: "\n" });
|
|
738
708
|
|
|
739
709
|
// Translate frame-relative row actions to absolute buffer rows by
|
|
740
710
|
// shifting by the vertical padding we just prepended. Columns are
|
|
@@ -775,32 +745,15 @@ function paint(dims?: { width: number; height: number }) {
|
|
|
775
745
|
((focusedIndex % targets.length) + targets.length) % targets.length;
|
|
776
746
|
}
|
|
777
747
|
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
// structuralKey — clock stamps stripped. Drives the animation.
|
|
783
|
-
// A clock tick alone does not flip this, so it
|
|
784
|
-
// updates silently; a real section data change
|
|
785
|
-
// does, and the slide fires.
|
|
748
|
+
// Taken BEFORE the focus highlight goes on top — fullKey captures
|
|
749
|
+
// everything (including the clock) that ultimately lands in the
|
|
750
|
+
// virtual buffer. Drives the setVirtualBufferContent skip check,
|
|
751
|
+
// so the clock still redraws in place every second.
|
|
786
752
|
const fullKey = JSON.stringify(entries);
|
|
787
|
-
const structuralKey = fullKey.replace(CLOCK_RE, "##:##:##");
|
|
788
753
|
const fullChanged = fullKey !== lastPaintedFullKey;
|
|
789
|
-
const structuralChanged = structuralKey !== lastPaintedStructuralKey;
|
|
790
754
|
const focusChanged = focusedIndex !== lastPaintedFocusedIndex;
|
|
791
|
-
|
|
792
|
-
//
|
|
793
|
-
// section data is unchanged. We repaint so the new layout takes
|
|
794
|
-
// effect but skip the slide — nothing NEW showed up, the user is
|
|
795
|
-
// just resizing a window. openDashboard clears lastPaintedW/H to
|
|
796
|
-
// -1 so the first paint after open doesn't trip this guard.
|
|
797
|
-
const dimsChanged =
|
|
798
|
-
lastPaintedW !== -1 &&
|
|
799
|
-
lastPaintedH !== -1 &&
|
|
800
|
-
(width !== lastPaintedW || height !== lastPaintedH);
|
|
801
|
-
|
|
802
|
-
// Identical render → short-circuit. Nothing to push to the
|
|
803
|
-
// buffer, nothing to animate.
|
|
755
|
+
|
|
756
|
+
// Identical render → short-circuit. Nothing to push to the buffer.
|
|
804
757
|
if (!fullChanged && !focusChanged) {
|
|
805
758
|
return;
|
|
806
759
|
}
|
|
@@ -838,25 +791,7 @@ function paint(dims?: { width: number; height: number }) {
|
|
|
838
791
|
lastPaintedW = width;
|
|
839
792
|
lastPaintedH = height;
|
|
840
793
|
lastPaintedFullKey = fullKey;
|
|
841
|
-
lastPaintedStructuralKey = structuralKey;
|
|
842
794
|
lastPaintedFocusedIndex = focusedIndex;
|
|
843
|
-
|
|
844
|
-
// Structural-change-driven re-animation: fire only when the
|
|
845
|
-
// section payload actually differs AND the dashboard isn't just
|
|
846
|
-
// reshaping in place (clock tick, focus move, and resize all
|
|
847
|
-
// land here without animating). Cancel any in-flight slide
|
|
848
|
-
// first so the new one snapshots the fresh content.
|
|
849
|
-
if (structuralChanged && !dimsChanged) {
|
|
850
|
-
if (activeAnimationId !== null) {
|
|
851
|
-
editor.cancelAnimation(activeAnimationId);
|
|
852
|
-
}
|
|
853
|
-
activeAnimationId = editor.animateVirtualBuffer(bufferId, {
|
|
854
|
-
kind: "slideIn",
|
|
855
|
-
from: resolveSlideFrom(),
|
|
856
|
-
durationMs: 520,
|
|
857
|
-
delayMs: 0,
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
795
|
}
|
|
861
796
|
|
|
862
797
|
// Open a URL in the user's browser via the platform's "open" helper.
|
|
@@ -1613,12 +1548,8 @@ async function openDashboard() {
|
|
|
1613
1548
|
}
|
|
1614
1549
|
|
|
1615
1550
|
// Clear the content/focus keys and dims so the first paint after
|
|
1616
|
-
// open is treated as a content change
|
|
1617
|
-
// Dim reset is needed because open is the one case where we DO
|
|
1618
|
-
// want the animation despite "dims changed" (there was no prior
|
|
1619
|
-
// dimension, so the change is really "buffer just appeared").
|
|
1551
|
+
// open is treated as a content change.
|
|
1620
1552
|
lastPaintedFullKey = null;
|
|
1621
|
-
lastPaintedStructuralKey = null;
|
|
1622
1553
|
lastPaintedFocusedIndex = -1;
|
|
1623
1554
|
lastPaintedW = -1;
|
|
1624
1555
|
lastPaintedH = -1;
|
|
@@ -1655,18 +1586,20 @@ async function dashboardShowOrFocus() {
|
|
|
1655
1586
|
registerHandler("dashboardShowOrFocus", dashboardShowOrFocus);
|
|
1656
1587
|
|
|
1657
1588
|
// Auto-open resolution: the session override (set via the exported
|
|
1658
|
-
// plugin API from init.ts) wins over the user
|
|
1659
|
-
//
|
|
1660
|
-
//
|
|
1661
|
-
|
|
1589
|
+
// plugin API from init.ts) wins over the user-configured value, which
|
|
1590
|
+
// comes from the typed plugin-config field declared below. The field
|
|
1591
|
+
// shows up in the Settings UI under "Plugin Settings → dashboard".
|
|
1592
|
+
editor.defineConfigBoolean("autoOpen", {
|
|
1593
|
+
default: true,
|
|
1594
|
+
description: "Show the dashboard automatically when Fresh starts with no real files open.",
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1662
1597
|
let autoOpenOverride: boolean | null = null;
|
|
1663
1598
|
|
|
1664
1599
|
function autoOpenEnabled(): boolean {
|
|
1665
1600
|
if (autoOpenOverride !== null) return autoOpenOverride;
|
|
1666
|
-
const cfg = editor.
|
|
1667
|
-
|
|
1668
|
-
const dashboard = plugins?.dashboard as Record<string, unknown> | undefined;
|
|
1669
|
-
return dashboard?.["auto-open"] !== false;
|
|
1601
|
+
const cfg = (editor.getPluginConfig() ?? {}) as { autoOpen?: boolean };
|
|
1602
|
+
return cfg.autoOpen !== false;
|
|
1670
1603
|
}
|
|
1671
1604
|
|
|
1672
1605
|
function shouldShowDashboard(): boolean {
|
|
@@ -1694,10 +1627,6 @@ registerHandler(
|
|
|
1694
1627
|
// If the dashboard itself was closed, clear our handle so we'll
|
|
1695
1628
|
// re-open on the next "last tab closed" event.
|
|
1696
1629
|
if (dashboardBufferId !== null && e.buffer_id === dashboardBufferId) {
|
|
1697
|
-
if (activeAnimationId !== null) {
|
|
1698
|
-
editor.cancelAnimation(activeAnimationId);
|
|
1699
|
-
activeAnimationId = null;
|
|
1700
|
-
}
|
|
1701
1630
|
dashboardBufferId = null;
|
|
1702
1631
|
return;
|
|
1703
1632
|
}
|
|
@@ -1717,10 +1646,6 @@ registerHandler(
|
|
|
1717
1646
|
"dashboardOnAfterFileOpen",
|
|
1718
1647
|
(_e: { buffer_id: number; path: string }) => {
|
|
1719
1648
|
if (dashboardBufferId === null) return;
|
|
1720
|
-
if (activeAnimationId !== null) {
|
|
1721
|
-
editor.cancelAnimation(activeAnimationId);
|
|
1722
|
-
activeAnimationId = null;
|
|
1723
|
-
}
|
|
1724
1649
|
editor.closeBuffer(dashboardBufferId);
|
|
1725
1650
|
dashboardBufferId = null;
|
|
1726
1651
|
},
|
|
@@ -1878,12 +1803,14 @@ editor.exportPluginApi("dashboard", {
|
|
|
1878
1803
|
// `plugins.dashboard.enabled` is true in the resolved config — so the
|
|
1879
1804
|
// standard settings UI is the single enable/disable surface.
|
|
1880
1805
|
//
|
|
1881
|
-
//
|
|
1882
|
-
//
|
|
1883
|
-
//
|
|
1884
|
-
//
|
|
1885
|
-
//
|
|
1886
|
-
//
|
|
1806
|
+
// Auto-open is driven exclusively by the `ready` hook (and the
|
|
1807
|
+
// `buffer_closed` handler for the last-tab-closed case). We
|
|
1808
|
+
// deliberately do NOT auto-open at module load: dashboard.ts loads
|
|
1809
|
+
// during the startup plugin batch, *before* the user's init.ts has
|
|
1810
|
+
// been evaluated, so an immediate auto-open would race
|
|
1811
|
+
// `setAutoOpen(false)` and dismiss the user's preference. Users who
|
|
1812
|
+
// hot-load the plugin mid-session (toggle on in Settings) get the
|
|
1813
|
+
// dashboard via the "Show Dashboard" command in the palette.
|
|
1887
1814
|
editor.on("ready", "dashboardOnReady");
|
|
1888
1815
|
editor.on("buffer_closed", "dashboardOnBufferClosed");
|
|
1889
1816
|
editor.on("viewport_changed", "dashboardOnViewportChanged");
|
|
@@ -1897,7 +1824,3 @@ editor.registerCommand(
|
|
|
1897
1824
|
"Open the dashboard, or bring it to the front if it's already open",
|
|
1898
1825
|
"dashboardShowOrFocus",
|
|
1899
1826
|
);
|
|
1900
|
-
|
|
1901
|
-
if (editor.listBuffers().length > 0 && shouldShowDashboard()) {
|
|
1902
|
-
openDashboard();
|
|
1903
|
-
}
|
package/plugins/flash.ts
CHANGED
|
@@ -33,7 +33,24 @@ const VTEXT_PREFIX = "flash-";
|
|
|
33
33
|
// the closest jump targets get the most comfortable keys. All
|
|
34
34
|
// lowercase: case-sensitive matching keeps the label letter from also
|
|
35
35
|
// being a valid pattern continuation, which matters for the skip rule.
|
|
36
|
-
|
|
36
|
+
editor.defineConfigString("labelPool", {
|
|
37
|
+
default: "asdfghjklqwertyuiopzxcvbnm",
|
|
38
|
+
description: "Characters used as jump labels, in comfort order. Labels are assigned to matches by distance from the cursor, so leftmost characters here land on the nearest matches.",
|
|
39
|
+
});
|
|
40
|
+
editor.defineConfigBoolean("skipRule", {
|
|
41
|
+
default: true,
|
|
42
|
+
description: "Skip a label character if it is also the next character after a match — prevents ambiguity between extending the search pattern and jumping.",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function flashSettings(): { labelPool: string; skipRule: boolean } {
|
|
46
|
+
const cfg = (editor.getPluginConfig() ?? {}) as { labelPool?: string; skipRule?: boolean };
|
|
47
|
+
return {
|
|
48
|
+
labelPool: cfg.labelPool && cfg.labelPool.length > 0
|
|
49
|
+
? cfg.labelPool
|
|
50
|
+
: "asdfghjklqwertyuiopzxcvbnm",
|
|
51
|
+
skipRule: cfg.skipRule ?? true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
37
54
|
|
|
38
55
|
interface Match {
|
|
39
56
|
/** Byte offset where the match starts in its buffer. */
|
|
@@ -334,9 +351,10 @@ function assignLabels(
|
|
|
334
351
|
prevLabelByKey: Map<string, string>,
|
|
335
352
|
): Match[] {
|
|
336
353
|
if (matches.length === 0) return matches;
|
|
337
|
-
const
|
|
354
|
+
const { labelPool, skipRule } = flashSettings();
|
|
355
|
+
const skip = skipRule ? buildSkipSet(matches, views, emptyPattern) : new Set<string>();
|
|
338
356
|
const remaining = new Set<string>();
|
|
339
|
-
for (const c of
|
|
357
|
+
for (const c of labelPool) if (!skip.has(c)) remaining.add(c);
|
|
340
358
|
|
|
341
359
|
const sorted = sortMatches(matches, startSplitId, startCursor);
|
|
342
360
|
|
|
@@ -353,7 +371,7 @@ function assignLabels(
|
|
|
353
371
|
// matches in distance order. Iterate the pool in its native
|
|
354
372
|
// (comfort-ranked) order so home-row letters go to nearest matches.
|
|
355
373
|
const orderedRemaining: string[] = [];
|
|
356
|
-
for (const c of
|
|
374
|
+
for (const c of labelPool) if (remaining.has(c)) orderedRemaining.push(c);
|
|
357
375
|
let next = 0;
|
|
358
376
|
for (const m of sorted) {
|
|
359
377
|
if (m.label) continue;
|
package/plugins/git_blame.ts
CHANGED
|
@@ -87,10 +87,13 @@ const blameState: BlameState = {
|
|
|
87
87
|
// Color Definitions for Header Styling
|
|
88
88
|
// =============================================================================
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
// Blame headers are rendered via `addVirtualLine`, which accepts theme
|
|
91
|
+
// keys directly — so we don't expose colors as plugin settings. Themes
|
|
92
|
+
// drive the look; if a theme lacks specific blame keys, these fall
|
|
93
|
+
// through to the editor's status-bar palette which is what every theme
|
|
94
|
+
// defines.
|
|
95
|
+
const HEADER_FG_KEY = "ui.status_bar_fg";
|
|
96
|
+
const HEADER_BG_KEY = "ui.status_bar_bg";
|
|
94
97
|
|
|
95
98
|
// =============================================================================
|
|
96
99
|
// Mode Definition
|
|
@@ -373,7 +376,8 @@ function addBlameHeaders(): void {
|
|
|
373
376
|
// Clear existing headers first
|
|
374
377
|
editor.clearVirtualTextNamespace(blameState.bufferId, BLAME_NAMESPACE);
|
|
375
378
|
|
|
376
|
-
// Add a virtual line above each block
|
|
379
|
+
// Add a virtual line above each block. Pass theme keys so the headers
|
|
380
|
+
// restyle automatically when the user switches themes.
|
|
377
381
|
for (const block of blameState.blocks) {
|
|
378
382
|
const headerText = formatBlockHeader(block);
|
|
379
383
|
|
|
@@ -381,7 +385,7 @@ function addBlameHeaders(): void {
|
|
|
381
385
|
blameState.bufferId,
|
|
382
386
|
block.startByte, // anchor position
|
|
383
387
|
headerText, // text content
|
|
384
|
-
{ fg:
|
|
388
|
+
{ fg: HEADER_FG_KEY, bg: HEADER_BG_KEY },
|
|
385
389
|
true, // above (LineAbove)
|
|
386
390
|
BLAME_NAMESPACE, // namespace for bulk removal
|
|
387
391
|
0 // priority
|