@fresh-editor/fresh-editor 0.3.6 → 0.3.8
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 +139 -0
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +112 -0
- package/plugins/audit_mode.ts +173 -6
- package/plugins/config-schema.json +34 -3
- package/plugins/dashboard.ts +18 -18
- package/plugins/env-manager.ts +168 -0
- package/plugins/flash.ts +22 -4
- package/plugins/git_blame.ts +10 -6
- package/plugins/git_log.ts +589 -196
- package/plugins/git_statusbar.i18n.json +72 -0
- package/plugins/git_statusbar.ts +133 -0
- package/plugins/lib/fresh.d.ts +412 -6
- package/plugins/lib/widgets.ts +111 -4
- package/plugins/live_diff.ts +168 -58
- package/plugins/merge_conflict.ts +89 -64
- package/plugins/orchestrator.ts +2174 -296
- package/plugins/pkg.ts +169 -4
- package/plugins/schemas/theme.schema.json +53 -0
- package/plugins/search_replace.i18n.json +140 -28
- package/plugins/search_replace.ts +674 -117
- package/plugins/tab_actions.i18n.json +212 -0
- package/plugins/tab_actions.ts +76 -0
- package/plugins/theme_editor.i18n.json +112 -84
- package/plugins/tsconfig.json +2 -0
- package/plugins/vi_mode.ts +11 -0
- package/themes/dark.json +1 -0
- package/themes/dracula.json +1 -0
- package/themes/high-contrast.json +1 -0
- package/themes/light.json +2 -1
- package/themes/nord.json +1 -0
- package/themes/nostalgia.json +1 -0
- package/themes/solarized-dark.json +1 -0
- package/themes/terminal.json +4 -3
|
@@ -100,33 +100,90 @@ const mergeState: MergeState = {
|
|
|
100
100
|
resultContent: "",
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
// Caches for the buffer_activated / after_file_open detection path.
|
|
104
|
+
// Without these, every tab switch re-spawns `git rev-parse` + `git ls-files`
|
|
105
|
+
// even though their answers rarely change. The detection path is purely a
|
|
106
|
+
// status-bar hint, so a stale answer for a few seconds is harmless.
|
|
107
|
+
const inGitRepoCache: Map<string, boolean> = new Map();
|
|
108
|
+
const detectionLastCheck: Map<string, number> = new Map();
|
|
109
|
+
const DETECTION_TTL_MS = 30_000;
|
|
110
|
+
const detectionInFlight: Map<string, Promise<void>> = new Map();
|
|
111
|
+
|
|
112
|
+
async function isInGitRepo(fileDir: string): Promise<boolean> {
|
|
113
|
+
const cached = inGitRepoCache.get(fileDir);
|
|
114
|
+
if (cached !== undefined) return cached;
|
|
115
|
+
const gitCheck = await editor.spawnProcess(
|
|
116
|
+
"git",
|
|
117
|
+
["rev-parse", "--is-inside-work-tree"],
|
|
118
|
+
fileDir,
|
|
119
|
+
);
|
|
120
|
+
const ok = gitCheck.exit_code === 0;
|
|
121
|
+
inGitRepoCache.set(fileDir, ok);
|
|
122
|
+
return ok;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function detectMergeConflictFor(
|
|
126
|
+
path: string,
|
|
127
|
+
statusOnDetect: () => string,
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
if (mergeState.isActive) return;
|
|
130
|
+
if (!path) return;
|
|
131
|
+
|
|
132
|
+
const last = detectionLastCheck.get(path);
|
|
133
|
+
if (last !== undefined && Date.now() - last < DETECTION_TTL_MS) return;
|
|
134
|
+
|
|
135
|
+
// Coalesce concurrent callers for the same path onto one in-flight check
|
|
136
|
+
// (e.g. buffer_activated + after_file_open firing back-to-back).
|
|
137
|
+
const existing = detectionInFlight.get(path);
|
|
138
|
+
if (existing) return existing;
|
|
139
|
+
|
|
140
|
+
const fileDir = editor.pathDirname(path);
|
|
141
|
+
const promise = (async () => {
|
|
142
|
+
try {
|
|
143
|
+
if (!(await isInGitRepo(fileDir))) return;
|
|
144
|
+
const lsFiles = await editor.spawnProcess(
|
|
145
|
+
"git",
|
|
146
|
+
["ls-files", "-u", path],
|
|
147
|
+
fileDir,
|
|
148
|
+
);
|
|
149
|
+
if (lsFiles.exit_code === 0 && lsFiles.stdout.trim().length > 0) {
|
|
150
|
+
editor.setStatus(statusOnDetect());
|
|
151
|
+
}
|
|
152
|
+
detectionLastCheck.set(path, Date.now());
|
|
153
|
+
} catch (_e) {
|
|
154
|
+
// Not in git repo or other error, ignore
|
|
155
|
+
} finally {
|
|
156
|
+
detectionInFlight.delete(path);
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
detectionInFlight.set(path, promise);
|
|
160
|
+
return promise;
|
|
161
|
+
}
|
|
162
|
+
|
|
103
163
|
// =============================================================================
|
|
104
164
|
// Color Definitions
|
|
105
165
|
// =============================================================================
|
|
106
166
|
|
|
167
|
+
// Theme keys preferred over hard-coded RGB so the look tracks the
|
|
168
|
+
// active theme. `addOverlay` accepts theme key strings directly.
|
|
169
|
+
const OURS_FG_KEY = "editor.diff_add_bg"; // green-ish in most themes
|
|
170
|
+
const THEIRS_FG_KEY = "editor.diff_remove_bg"; // red-ish
|
|
171
|
+
const UNRESOLVED_FG_KEY = "ui.file_status_conflicted_fg";
|
|
172
|
+
|
|
107
173
|
const colors = {
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
diffMod: [50, 50, 100] as [number, number, number], // Blue for modifications
|
|
122
|
-
|
|
123
|
-
// Selection
|
|
124
|
-
selected: [80, 80, 120] as [number, number, number], // Selection highlight
|
|
125
|
-
|
|
126
|
-
// Buttons/actions
|
|
127
|
-
button: [100, 149, 237] as [number, number, number], // Cornflower blue
|
|
128
|
-
resolved: [100, 200, 100] as [number, number, number], // Green for resolved
|
|
129
|
-
unresolved: [200, 100, 100] as [number, number, number], // Red for unresolved
|
|
174
|
+
// RGB tuples retained for any future contributor who reaches for them
|
|
175
|
+
// — the active call sites use the theme keys above. None of these
|
|
176
|
+
// appear in the currently rendered UI.
|
|
177
|
+
oursHeader: [100, 200, 255] as [number, number, number],
|
|
178
|
+
theirsHeader: [255, 180, 100] as [number, number, number],
|
|
179
|
+
resultHeader: [150, 255, 150] as [number, number, number],
|
|
180
|
+
conflictBase: [70, 70, 70] as [number, number, number],
|
|
181
|
+
diffAdd: [50, 100, 50] as [number, number, number],
|
|
182
|
+
diffDel: [100, 50, 50] as [number, number, number],
|
|
183
|
+
diffMod: [50, 50, 100] as [number, number, number],
|
|
184
|
+
selected: [80, 80, 120] as [number, number, number],
|
|
185
|
+
button: [100, 149, 237] as [number, number, number],
|
|
186
|
+
resolved: [100, 200, 100] as [number, number, number],
|
|
130
187
|
};
|
|
131
188
|
|
|
132
189
|
// =============================================================================
|
|
@@ -950,7 +1007,7 @@ function highlightPanel(bufferId: number, side: "ours" | "theirs"): void {
|
|
|
950
1007
|
const lines = content.split("\n");
|
|
951
1008
|
|
|
952
1009
|
let byteOffset = 0;
|
|
953
|
-
const conflictColor = side === "ours" ?
|
|
1010
|
+
const conflictColor = side === "ours" ? OURS_FG_KEY : THEIRS_FG_KEY;
|
|
954
1011
|
|
|
955
1012
|
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
956
1013
|
const line = lines[lineIdx];
|
|
@@ -989,7 +1046,7 @@ function highlightResultPanel(bufferId: number): void {
|
|
|
989
1046
|
// Highlight conflict markers
|
|
990
1047
|
if (line.startsWith("<<<<<<<") || line.startsWith("=======") || line.startsWith(">>>>>>>")) {
|
|
991
1048
|
editor.addOverlay(bufferId, `merge-marker-${lineIdx}`, lineStart, lineEnd, {
|
|
992
|
-
fg:
|
|
1049
|
+
fg: UNRESOLVED_FG_KEY,
|
|
993
1050
|
underline: true,
|
|
994
1051
|
});
|
|
995
1052
|
}
|
|
@@ -1712,50 +1769,18 @@ registerHandler("merge_show_help", merge_show_help);
|
|
|
1712
1769
|
// =============================================================================
|
|
1713
1770
|
|
|
1714
1771
|
editor.on("buffer_activated", async (data) => {
|
|
1715
|
-
// Don't trigger if already in merge mode
|
|
1716
|
-
if (mergeState.isActive) return;
|
|
1717
|
-
|
|
1718
|
-
// Don't trigger for virtual buffers
|
|
1719
1772
|
const info = editor.getBufferInfo(data.buffer_id);
|
|
1720
1773
|
if (!info || !info.path) return;
|
|
1721
|
-
|
|
1722
|
-
// Get the directory of the file for running git commands
|
|
1723
|
-
const fileDir = editor.pathDirname(info.path);
|
|
1724
|
-
|
|
1725
|
-
// Check if we're in a git repo first
|
|
1726
|
-
try {
|
|
1727
|
-
const gitCheck = await editor.spawnProcess("git", ["rev-parse", "--is-inside-work-tree"], fileDir);
|
|
1728
|
-
if (gitCheck.exit_code !== 0) return;
|
|
1729
|
-
|
|
1730
|
-
// Check for unmerged entries
|
|
1731
|
-
const lsFiles = await editor.spawnProcess("git", ["ls-files", "-u", info.path], fileDir);
|
|
1732
|
-
if (lsFiles.exit_code === 0 && lsFiles.stdout.trim().length > 0) {
|
|
1733
|
-
editor.setStatus(editor.t("status.detected"));
|
|
1734
|
-
}
|
|
1735
|
-
} catch (e) {
|
|
1736
|
-
// Not in git repo or other error, ignore
|
|
1737
|
-
}
|
|
1774
|
+
await detectMergeConflictFor(info.path, () => editor.t("status.detected"));
|
|
1738
1775
|
});
|
|
1739
1776
|
editor.on("after_file_open", async (data) => {
|
|
1740
|
-
//
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
try {
|
|
1748
|
-
const gitCheck = await editor.spawnProcess("git", ["rev-parse", "--is-inside-work-tree"], fileDir);
|
|
1749
|
-
if (gitCheck.exit_code !== 0) return;
|
|
1750
|
-
|
|
1751
|
-
// Check for unmerged entries
|
|
1752
|
-
const lsFiles = await editor.spawnProcess("git", ["ls-files", "-u", data.path], fileDir);
|
|
1753
|
-
if (lsFiles.exit_code === 0 && lsFiles.stdout.trim().length > 0) {
|
|
1754
|
-
editor.setStatus(editor.t("status.detected_file", { path: data.path }));
|
|
1755
|
-
}
|
|
1756
|
-
} catch (e) {
|
|
1757
|
-
// Not in git repo or other error, ignore
|
|
1758
|
-
}
|
|
1777
|
+
// File-open is a strong signal that state may have changed externally
|
|
1778
|
+
// (e.g. user ran `git merge` then opened the conflicted file). Bypass the
|
|
1779
|
+
// per-path TTL so the detection still runs.
|
|
1780
|
+
detectionLastCheck.delete(data.path);
|
|
1781
|
+
await detectMergeConflictFor(data.path, () =>
|
|
1782
|
+
editor.t("status.detected_file", { path: data.path }),
|
|
1783
|
+
);
|
|
1759
1784
|
});
|
|
1760
1785
|
|
|
1761
1786
|
// =============================================================================
|