@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.
@@ -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
- // Panel headers
109
- oursHeader: [100, 200, 255] as [number, number, number], // Cyan for OURS
110
- theirsHeader: [255, 180, 100] as [number, number, number], // Orange for THEIRS
111
- resultHeader: [150, 255, 150] as [number, number, number], // Green for RESULT
112
-
113
- // Conflict highlighting
114
- conflictOurs: [50, 80, 100] as [number, number, number], // Blue-tinted background
115
- conflictTheirs: [100, 70, 50] as [number, number, number], // Orange-tinted background
116
- conflictBase: [70, 70, 70] as [number, number, number], // Gray for base
117
-
118
- // Intra-line diff colors
119
- diffAdd: [50, 100, 50] as [number, number, number], // Green for additions
120
- diffDel: [100, 50, 50] as [number, number, number], // Red for deletions
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" ? colors.conflictOurs : colors.conflictTheirs;
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: colors.unresolved,
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
- // Don't trigger if already in merge mode
1741
- if (mergeState.isActive) return;
1742
-
1743
- // Get the directory of the file for running git commands
1744
- const fileDir = editor.pathDirname(data.path);
1745
-
1746
- // Check if we're in a git repo first
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
  // =============================================================================