@fresh-editor/fresh-editor 0.1.70 → 0.1.74

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.
@@ -1009,6 +1009,16 @@ interface SideBySideDiffState {
1009
1009
  let activeSideBySideState: SideBySideDiffState | null = null;
1010
1010
  let nextScrollSyncGroupId = 1;
1011
1011
 
1012
+ // State for composite buffer-based diff view
1013
+ interface CompositeDiffState {
1014
+ compositeBufferId: number;
1015
+ oldBufferId: number;
1016
+ newBufferId: number;
1017
+ filePath: string;
1018
+ }
1019
+
1020
+ let activeCompositeDiffState: CompositeDiffState | null = null;
1021
+
1012
1022
  globalThis.review_drill_down = async () => {
1013
1023
  const bid = editor.getActiveBufferId();
1014
1024
  const props = editor.getTextPropertiesAtCursor(bid);
@@ -1048,21 +1058,9 @@ globalThis.review_drill_down = async () => {
1048
1058
  return;
1049
1059
  }
1050
1060
 
1051
- // Close the Review Diff buffer to make room for side-by-side view
1052
- // Store the review buffer ID so we can restore it later
1053
- const reviewBufferId = bid;
1054
-
1055
- // Compute aligned diff for the FULL file with all hunks
1056
- const alignedLines = computeFullFileAlignedDiff(oldContent, newContent, fileHunks);
1057
-
1058
- // Generate content for both panes
1059
- const oldPane = generateDiffPaneContent(alignedLines, 'old');
1060
- const newPane = generateDiffPaneContent(alignedLines, 'new');
1061
-
1062
- // Close any existing side-by-side views
1061
+ // Close any existing side-by-side views (old split-based approach)
1063
1062
  if (activeSideBySideState) {
1064
1063
  try {
1065
- // Remove scroll sync group first
1066
1064
  if (activeSideBySideState.scrollSyncGroupId !== null) {
1067
1065
  (editor as any).removeScrollSyncGroup(activeSideBySideState.scrollSyncGroupId);
1068
1066
  }
@@ -1072,143 +1070,133 @@ globalThis.review_drill_down = async () => {
1072
1070
  activeSideBySideState = null;
1073
1071
  }
1074
1072
 
1075
- // Get the current split ID before closing the Review Diff buffer
1076
- const currentSplitId = (editor as any).getActiveSplitId();
1073
+ // Close any existing composite diff view
1074
+ if (activeCompositeDiffState) {
1075
+ try {
1076
+ editor.closeCompositeBuffer(activeCompositeDiffState.compositeBufferId);
1077
+ editor.closeBuffer(activeCompositeDiffState.oldBufferId);
1078
+ editor.closeBuffer(activeCompositeDiffState.newBufferId);
1079
+ } catch {}
1080
+ activeCompositeDiffState = null;
1081
+ }
1082
+
1083
+ // Create virtual buffers for old and new content
1084
+ const oldLines = oldContent.split('\n');
1085
+ const newLines = newContent.split('\n');
1077
1086
 
1078
- // Create OLD buffer in the CURRENT split (replaces Review Diff)
1079
- const oldBufferId = await editor.createVirtualBufferInExistingSplit({
1080
- name: `[OLD] ${h.file}`,
1081
- mode: "diff-view",
1087
+ const oldEntries: TextPropertyEntry[] = oldLines.map((line, idx) => ({
1088
+ text: line + '\n',
1089
+ properties: { type: 'line', lineNum: idx + 1 }
1090
+ }));
1091
+
1092
+ const newEntries: TextPropertyEntry[] = newLines.map((line, idx) => ({
1093
+ text: line + '\n',
1094
+ properties: { type: 'line', lineNum: idx + 1 }
1095
+ }));
1096
+
1097
+ // Create source buffers (hidden from tabs, used by composite)
1098
+ const oldBufferId = await editor.createVirtualBuffer({
1099
+ name: `*OLD:${h.file}*`,
1100
+ mode: "normal",
1082
1101
  read_only: true,
1102
+ entries: oldEntries,
1103
+ show_line_numbers: true,
1083
1104
  editing_disabled: true,
1084
- entries: oldPane.entries,
1085
- split_id: currentSplitId,
1086
- show_line_numbers: false,
1087
- line_wrap: false
1105
+ hidden_from_tabs: true
1088
1106
  });
1089
- const oldSplitId = currentSplitId;
1090
-
1091
- // Close the Review Diff buffer after showing OLD
1092
- editor.closeBuffer(reviewBufferId);
1093
-
1094
- // Apply highlights to old pane
1095
- editor.clearNamespace(oldBufferId, "diff-view");
1096
- for (const hl of oldPane.highlights) {
1097
- const bg = hl.bg || [-1, -1, -1];
1098
- editor.addOverlay(
1099
- oldBufferId, "diff-view",
1100
- hl.range[0], hl.range[1],
1101
- hl.fg[0], hl.fg[1], hl.fg[2],
1102
- false, hl.bold || false, false,
1103
- bg[0], bg[1], bg[2],
1104
- hl.extend_to_line_end || false
1105
- );
1106
- }
1107
1107
 
1108
- // Step 2: Create NEW pane in a vertical split (RIGHT of OLD)
1109
- const newRes = await editor.createVirtualBufferInSplit({
1110
- name: `[NEW] ${h.file}`,
1111
- mode: "diff-view",
1108
+ const newBufferId = await editor.createVirtualBuffer({
1109
+ name: `*NEW:${h.file}*`,
1110
+ mode: "normal",
1112
1111
  read_only: true,
1112
+ entries: newEntries,
1113
+ show_line_numbers: true,
1113
1114
  editing_disabled: true,
1114
- entries: newPane.entries,
1115
- ratio: 0.5,
1116
- direction: "vertical",
1117
- show_line_numbers: false,
1118
- line_wrap: false
1115
+ hidden_from_tabs: true
1119
1116
  });
1120
- const newBufferId = newRes.buffer_id;
1121
- const newSplitId = newRes.split_id!;
1122
-
1123
- // Apply highlights to new pane
1124
- editor.clearNamespace(newBufferId, "diff-view");
1125
- for (const hl of newPane.highlights) {
1126
- const bg = hl.bg || [-1, -1, -1];
1127
- editor.addOverlay(
1128
- newBufferId, "diff-view",
1129
- hl.range[0], hl.range[1],
1130
- hl.fg[0], hl.fg[1], hl.fg[2],
1131
- false, hl.bold || false, false,
1132
- bg[0], bg[1], bg[2],
1133
- hl.extend_to_line_end || false
1134
- );
1135
- }
1136
1117
 
1137
- // Focus OLD pane (left) - convention is to start on old side
1138
- (editor as any).focusSplit(oldSplitId);
1139
-
1140
- // Set up core-handled scroll sync using the new anchor-based API
1141
- // This replaces the old viewport_changed hook approach
1142
- let scrollSyncGroupId: number | null = null;
1143
- try {
1144
- // Generate a unique group ID
1145
- scrollSyncGroupId = nextScrollSyncGroupId++;
1146
- (editor as any).createScrollSyncGroup(scrollSyncGroupId, oldSplitId, newSplitId);
1147
-
1148
- // Compute sync anchors from aligned lines
1149
- // Each aligned line is a sync point - we map line indices to anchors
1150
- // For the new core sync, we use line numbers (not byte offsets)
1151
- const anchors: [number, number][] = [];
1152
- for (let i = 0; i < alignedLines.length; i++) {
1153
- // Add anchors at meaningful boundaries: start of file, and at change boundaries
1154
- const line = alignedLines[i];
1155
- const prevLine = i > 0 ? alignedLines[i - 1] : null;
1156
-
1157
- // Add anchor at start of file
1158
- if (i === 0) {
1159
- anchors.push([0, 0]);
1160
- }
1161
-
1162
- // Add anchor at change boundaries (when change type changes)
1163
- if (prevLine && prevLine.changeType !== line.changeType) {
1164
- anchors.push([i, i]);
1165
- }
1166
- }
1167
-
1168
- // Add anchor at end
1169
- if (alignedLines.length > 0) {
1170
- anchors.push([alignedLines.length, alignedLines.length]);
1118
+ // Convert hunks to composite buffer format (parse counts from git diff)
1119
+ const compositeHunks: TsCompositeHunk[] = fileHunks.map(fh => {
1120
+ // Parse actual counts from the hunk lines
1121
+ let oldCount = 0, newCount = 0;
1122
+ for (const line of fh.lines) {
1123
+ if (line.startsWith('-')) oldCount++;
1124
+ else if (line.startsWith('+')) newCount++;
1125
+ else if (line.startsWith(' ')) { oldCount++; newCount++; }
1171
1126
  }
1127
+ return {
1128
+ old_start: fh.oldRange.start - 1, // Convert to 0-indexed
1129
+ old_count: oldCount || 1,
1130
+ new_start: fh.range.start - 1, // Convert to 0-indexed
1131
+ new_count: newCount || 1
1132
+ };
1133
+ });
1172
1134
 
1173
- (editor as any).setScrollSyncAnchors(scrollSyncGroupId, anchors);
1174
- } catch (e) {
1175
- editor.debug(`Failed to create scroll sync group: ${e}`);
1176
- scrollSyncGroupId = null;
1177
- }
1135
+ // Create composite buffer with side-by-side layout
1136
+ const compositeBufferId = await editor.createCompositeBuffer({
1137
+ name: `*Diff: ${h.file}*`,
1138
+ mode: "diff-view",
1139
+ layout: {
1140
+ layout_type: "side-by-side",
1141
+ ratios: [0.5, 0.5],
1142
+ show_separator: true
1143
+ },
1144
+ sources: [
1145
+ {
1146
+ buffer_id: oldBufferId,
1147
+ label: "OLD (HEAD)",
1148
+ editable: false,
1149
+ style: {
1150
+ remove_bg: [80, 40, 40],
1151
+ gutter_style: "diff-markers"
1152
+ }
1153
+ },
1154
+ {
1155
+ buffer_id: newBufferId,
1156
+ label: "NEW (Working)",
1157
+ editable: false,
1158
+ style: {
1159
+ add_bg: [40, 80, 40],
1160
+ gutter_style: "diff-markers"
1161
+ }
1162
+ }
1163
+ ],
1164
+ hunks: compositeHunks.length > 0 ? compositeHunks : null
1165
+ });
1178
1166
 
1179
- // Store state for synchronized scrolling
1180
- activeSideBySideState = {
1181
- oldSplitId,
1182
- newSplitId,
1167
+ // Store state for cleanup
1168
+ activeCompositeDiffState = {
1169
+ compositeBufferId,
1183
1170
  oldBufferId,
1184
1171
  newBufferId,
1185
- alignedLines,
1186
- oldLineByteOffsets: oldPane.lineByteOffsets,
1187
- newLineByteOffsets: newPane.lineByteOffsets,
1188
- scrollSyncGroupId
1172
+ filePath: h.file
1189
1173
  };
1190
- activeDiffViewState = { lSplit: oldSplitId, rSplit: newSplitId };
1191
1174
 
1192
- const addedLines = alignedLines.filter(l => l.changeType === 'added').length;
1193
- const removedLines = alignedLines.filter(l => l.changeType === 'removed').length;
1194
- const modifiedLines = alignedLines.filter(l => l.changeType === 'modified').length;
1195
- editor.setStatus(editor.t("status.diff_summary", { added: String(addedLines), removed: String(removedLines), modified: String(modifiedLines) }));
1175
+ // Show the composite buffer (replaces the review diff buffer)
1176
+ editor.showBuffer(compositeBufferId);
1177
+
1178
+ const addedCount = fileHunks.reduce((sum, fh) => {
1179
+ return sum + fh.lines.filter(l => l.startsWith('+')).length;
1180
+ }, 0);
1181
+ const removedCount = fileHunks.reduce((sum, fh) => {
1182
+ return sum + fh.lines.filter(l => l.startsWith('-')).length;
1183
+ }, 0);
1184
+ const modifiedCount = Math.min(addedCount, removedCount);
1185
+
1186
+ editor.setStatus(editor.t("status.diff_summary", { added: String(addedCount), removed: String(removedCount), modified: String(modifiedCount) }));
1196
1187
  }
1197
1188
  };
1198
1189
 
1199
- // Define the diff-view mode with navigation keys
1200
- editor.defineMode("diff-view", "special", [
1201
- ["q", "close_buffer"],
1202
- ["j", "move_down"],
1203
- ["k", "move_up"],
1204
- ["g", "move_document_start"],
1205
- ["G", "move_document_end"],
1206
- ["C-d", "move_page_down"],
1207
- ["C-u", "move_page_up"],
1208
- ["Down", "move_down"],
1209
- ["Up", "move_up"],
1210
- ["PageDown", "move_page_down"],
1211
- ["PageUp", "move_page_up"],
1190
+ // Define the diff-view mode - inherits from "normal" for all standard navigation/selection/copy
1191
+ // Only adds diff-specific keybindings (close, hunk navigation)
1192
+ editor.defineMode("diff-view", "normal", [
1193
+ // Close the diff view
1194
+ ["q", "close"],
1195
+ // Hunk navigation (diff-specific)
1196
+ ["n", "review_next_hunk"],
1197
+ ["p", "review_prev_hunk"],
1198
+ ["]", "review_next_hunk"],
1199
+ ["[", "review_prev_hunk"],
1212
1200
  ], true);
1213
1201
 
1214
1202
  // --- Review Comment Actions ---
@@ -1528,7 +1516,7 @@ globalThis.on_review_buffer_closed = (data: any) => {
1528
1516
  if (data.buffer_id === state.reviewBufferId) stop_review_diff();
1529
1517
  };
1530
1518
 
1531
- // Side-by-side diff for current file (can be called directly without Review Diff mode)
1519
+ // Side-by-side diff for current file using composite buffers
1532
1520
  globalThis.side_by_side_diff_current_file = async () => {
1533
1521
  const bid = editor.getActiveBufferId();
1534
1522
  const absolutePath = editor.getBufferPath(bid);
@@ -1584,20 +1572,22 @@ globalThis.side_by_side_diff_current_file = async () => {
1584
1572
 
1585
1573
  for (const line of lines) {
1586
1574
  if (line.startsWith('@@')) {
1587
- const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@(.*)/);
1575
+ const match = line.match(/@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@(.*)/);
1588
1576
  if (match) {
1589
1577
  const oldStart = parseInt(match[1]);
1590
- const newStart = parseInt(match[2]);
1578
+ const oldCount = match[2] ? parseInt(match[2]) : 1;
1579
+ const newStart = parseInt(match[3]);
1580
+ const newCount = match[4] ? parseInt(match[4]) : 1;
1591
1581
  currentHunk = {
1592
1582
  id: `${filePath}:${newStart}`,
1593
1583
  file: filePath,
1594
- range: { start: newStart, end: newStart },
1595
- oldRange: { start: oldStart, end: oldStart },
1584
+ range: { start: newStart, end: newStart + newCount - 1 },
1585
+ oldRange: { start: oldStart, end: oldStart + oldCount - 1 },
1596
1586
  type: 'modify',
1597
1587
  lines: [],
1598
1588
  status: 'pending',
1599
1589
  reviewStatus: 'pending',
1600
- contextHeader: match[3]?.trim() || "",
1590
+ contextHeader: match[5]?.trim() || "",
1601
1591
  byteOffset: 0
1602
1592
  };
1603
1593
  fileHunks.push(currentHunk);
@@ -1631,13 +1621,6 @@ globalThis.side_by_side_diff_current_file = async () => {
1631
1621
  return;
1632
1622
  }
1633
1623
 
1634
- // Compute aligned diff for the FULL file
1635
- const alignedLines = computeFullFileAlignedDiff(oldContent, newContent, fileHunks);
1636
-
1637
- // Generate content for both panes
1638
- const oldPane = generateDiffPaneContent(alignedLines, 'old');
1639
- const newPane = generateDiffPaneContent(alignedLines, 'new');
1640
-
1641
1624
  // Close any existing side-by-side views
1642
1625
  if (activeSideBySideState) {
1643
1626
  try {
@@ -1650,109 +1633,111 @@ globalThis.side_by_side_diff_current_file = async () => {
1650
1633
  activeSideBySideState = null;
1651
1634
  }
1652
1635
 
1653
- // Get the current split ID
1654
- const currentSplitId = (editor as any).getActiveSplitId();
1636
+ // Close any existing composite diff view
1637
+ if (activeCompositeDiffState) {
1638
+ try {
1639
+ editor.closeCompositeBuffer(activeCompositeDiffState.compositeBufferId);
1640
+ editor.closeBuffer(activeCompositeDiffState.oldBufferId);
1641
+ editor.closeBuffer(activeCompositeDiffState.newBufferId);
1642
+ } catch {}
1643
+ activeCompositeDiffState = null;
1644
+ }
1655
1645
 
1656
- // Create OLD buffer in the CURRENT split
1657
- const oldBufferId = await editor.createVirtualBufferInExistingSplit({
1658
- name: `[OLD] ${filePath}`,
1659
- mode: "diff-view",
1646
+ // Create virtual buffers for old and new content
1647
+ const oldLines = oldContent.split('\n');
1648
+ const newLines = newContent.split('\n');
1649
+
1650
+ const oldEntries: TextPropertyEntry[] = oldLines.map((line, idx) => ({
1651
+ text: line + '\n',
1652
+ properties: { type: 'line', lineNum: idx + 1 }
1653
+ }));
1654
+
1655
+ const newEntries: TextPropertyEntry[] = newLines.map((line, idx) => ({
1656
+ text: line + '\n',
1657
+ properties: { type: 'line', lineNum: idx + 1 }
1658
+ }));
1659
+
1660
+ // Create source buffers (hidden from tabs, used by composite)
1661
+ const oldBufferId = await editor.createVirtualBuffer({
1662
+ name: `*OLD:${filePath}*`,
1663
+ mode: "normal",
1660
1664
  read_only: true,
1665
+ entries: oldEntries,
1666
+ show_line_numbers: true,
1661
1667
  editing_disabled: true,
1662
- entries: oldPane.entries,
1663
- split_id: currentSplitId,
1664
- show_line_numbers: false,
1665
- line_wrap: false
1668
+ hidden_from_tabs: true
1666
1669
  });
1667
- const oldSplitId = currentSplitId;
1668
-
1669
- // Apply highlights to old pane
1670
- editor.clearNamespace(oldBufferId, "diff-view");
1671
- for (const hl of oldPane.highlights) {
1672
- const bg = hl.bg || [-1, -1, -1];
1673
- editor.addOverlay(
1674
- oldBufferId, "diff-view",
1675
- hl.range[0], hl.range[1],
1676
- hl.fg[0], hl.fg[1], hl.fg[2],
1677
- false, hl.bold || false, false,
1678
- bg[0], bg[1], bg[2],
1679
- hl.extend_to_line_end || false
1680
- );
1681
- }
1682
1670
 
1683
- // Create NEW pane in a vertical split (RIGHT of OLD)
1684
- const newRes = await editor.createVirtualBufferInSplit({
1685
- name: `[NEW] ${filePath}`,
1686
- mode: "diff-view",
1671
+ const newBufferId = await editor.createVirtualBuffer({
1672
+ name: `*NEW:${filePath}*`,
1673
+ mode: "normal",
1687
1674
  read_only: true,
1675
+ entries: newEntries,
1676
+ show_line_numbers: true,
1688
1677
  editing_disabled: true,
1689
- entries: newPane.entries,
1690
- ratio: 0.5,
1691
- direction: "vertical",
1692
- show_line_numbers: false,
1693
- line_wrap: false
1678
+ hidden_from_tabs: true
1694
1679
  });
1695
- const newBufferId = newRes.buffer_id;
1696
- const newSplitId = newRes.split_id!;
1697
1680
 
1698
- // Apply highlights to new pane
1699
- editor.clearNamespace(newBufferId, "diff-view");
1700
- for (const hl of newPane.highlights) {
1701
- const bg = hl.bg || [-1, -1, -1];
1702
- editor.addOverlay(
1703
- newBufferId, "diff-view",
1704
- hl.range[0], hl.range[1],
1705
- hl.fg[0], hl.fg[1], hl.fg[2],
1706
- false, hl.bold || false, false,
1707
- bg[0], bg[1], bg[2],
1708
- hl.extend_to_line_end || false
1709
- );
1710
- }
1711
-
1712
- // Focus OLD pane (left)
1713
- (editor as any).focusSplit(oldSplitId);
1714
-
1715
- // Set up scroll sync
1716
- let scrollSyncGroupId: number | null = null;
1717
- try {
1718
- scrollSyncGroupId = nextScrollSyncGroupId++;
1719
- (editor as any).createScrollSyncGroup(scrollSyncGroupId, oldSplitId, newSplitId);
1720
-
1721
- const anchors: [number, number][] = [];
1722
- for (let i = 0; i < alignedLines.length; i++) {
1723
- const line = alignedLines[i];
1724
- const prevLine = i > 0 ? alignedLines[i - 1] : null;
1725
- if (i === 0) anchors.push([0, 0]);
1726
- if (prevLine && prevLine.changeType !== line.changeType) {
1727
- anchors.push([i, i]);
1681
+ // Convert hunks to composite buffer format
1682
+ const compositeHunks: TsCompositeHunk[] = fileHunks.map(h => ({
1683
+ old_start: h.oldRange.start - 1, // Convert to 0-indexed
1684
+ old_count: h.oldRange.end - h.oldRange.start + 1,
1685
+ new_start: h.range.start - 1, // Convert to 0-indexed
1686
+ new_count: h.range.end - h.range.start + 1
1687
+ }));
1688
+
1689
+ // Create composite buffer with side-by-side layout
1690
+ const compositeBufferId = await editor.createCompositeBuffer({
1691
+ name: `*Diff: ${filePath}*`,
1692
+ mode: "diff-view",
1693
+ layout: {
1694
+ layout_type: "side-by-side",
1695
+ ratios: [0.5, 0.5],
1696
+ show_separator: true
1697
+ },
1698
+ sources: [
1699
+ {
1700
+ buffer_id: oldBufferId,
1701
+ label: "OLD (HEAD)",
1702
+ editable: false,
1703
+ style: {
1704
+ remove_bg: [80, 40, 40],
1705
+ gutter_style: "diff-markers"
1706
+ }
1707
+ },
1708
+ {
1709
+ buffer_id: newBufferId,
1710
+ label: "NEW (Working)",
1711
+ editable: false,
1712
+ style: {
1713
+ add_bg: [40, 80, 40],
1714
+ gutter_style: "diff-markers"
1715
+ }
1728
1716
  }
1729
- }
1730
- if (alignedLines.length > 0) {
1731
- anchors.push([alignedLines.length, alignedLines.length]);
1732
- }
1733
- (editor as any).setScrollSyncAnchors(scrollSyncGroupId, anchors);
1734
- } catch (e) {
1735
- editor.debug(`Failed to create scroll sync group: ${e}`);
1736
- scrollSyncGroupId = null;
1737
- }
1717
+ ],
1718
+ hunks: compositeHunks.length > 0 ? compositeHunks : null
1719
+ });
1738
1720
 
1739
- // Store state
1740
- activeSideBySideState = {
1741
- oldSplitId,
1742
- newSplitId,
1721
+ // Store state for cleanup
1722
+ activeCompositeDiffState = {
1723
+ compositeBufferId,
1743
1724
  oldBufferId,
1744
1725
  newBufferId,
1745
- alignedLines,
1746
- oldLineByteOffsets: oldPane.lineByteOffsets,
1747
- newLineByteOffsets: newPane.lineByteOffsets,
1748
- scrollSyncGroupId
1726
+ filePath
1749
1727
  };
1750
- activeDiffViewState = { lSplit: oldSplitId, rSplit: newSplitId };
1751
1728
 
1752
- const addedLines = alignedLines.filter(l => l.changeType === 'added').length;
1753
- const removedLines = alignedLines.filter(l => l.changeType === 'removed').length;
1754
- const modifiedLines = alignedLines.filter(l => l.changeType === 'modified').length;
1755
- editor.setStatus(editor.t("status.diff_summary", { added: String(addedLines), removed: String(removedLines), modified: String(modifiedLines) }));
1729
+ // Show the composite buffer
1730
+ editor.showBuffer(compositeBufferId);
1731
+
1732
+ const addedCount = fileHunks.reduce((sum, h) => {
1733
+ return sum + h.lines.filter(l => l.startsWith('+')).length;
1734
+ }, 0);
1735
+ const removedCount = fileHunks.reduce((sum, h) => {
1736
+ return sum + h.lines.filter(l => l.startsWith('-')).length;
1737
+ }, 0);
1738
+ const modifiedCount = Math.min(addedCount, removedCount);
1739
+
1740
+ editor.setStatus(editor.t("status.diff_summary", { added: String(addedCount), removed: String(removedCount), modified: String(modifiedCount) }));
1756
1741
  };
1757
1742
 
1758
1743
  // Register Modes and Commands
@@ -1772,7 +1757,7 @@ editor.registerCommand("%cmd.overall_feedback", "%cmd.overall_feedback_desc", "r
1772
1757
  editor.registerCommand("%cmd.export_markdown", "%cmd.export_markdown_desc", "review_export_session", "review-mode");
1773
1758
  editor.registerCommand("%cmd.export_json", "%cmd.export_json_desc", "review_export_json", "review-mode");
1774
1759
 
1775
- // Handler for when buffers are closed - cleans up scroll sync groups
1760
+ // Handler for when buffers are closed - cleans up scroll sync groups and composite buffers
1776
1761
  globalThis.on_buffer_closed = (data: any) => {
1777
1762
  // If one of the diff view buffers is closed, clean up the scroll sync group
1778
1763
  if (activeSideBySideState) {
@@ -1788,6 +1773,18 @@ globalThis.on_buffer_closed = (data: any) => {
1788
1773
  activeDiffViewState = null;
1789
1774
  }
1790
1775
  }
1776
+
1777
+ // Clean up composite diff state if the composite buffer is closed
1778
+ if (activeCompositeDiffState) {
1779
+ if (data.buffer_id === activeCompositeDiffState.compositeBufferId) {
1780
+ // Close the source buffers
1781
+ try {
1782
+ editor.closeBuffer(activeCompositeDiffState.oldBufferId);
1783
+ editor.closeBuffer(activeCompositeDiffState.newBufferId);
1784
+ } catch {}
1785
+ activeCompositeDiffState = null;
1786
+ }
1787
+ }
1791
1788
  };
1792
1789
 
1793
1790
  editor.on("buffer_closed", "on_buffer_closed");
@@ -1797,7 +1794,7 @@ editor.defineMode("review-mode", "normal", [
1797
1794
  ["s", "review_stage_hunk"], ["d", "review_discard_hunk"],
1798
1795
  // Navigation
1799
1796
  ["n", "review_next_hunk"], ["p", "review_prev_hunk"], ["r", "review_refresh"],
1800
- ["Enter", "review_drill_down"], ["q", "close_buffer"],
1797
+ ["Enter", "review_drill_down"], ["q", "close"],
1801
1798
  // Review actions
1802
1799
  ["c", "review_add_comment"],
1803
1800
  ["a", "review_approve_hunk"],
@@ -4,26 +4,61 @@
4
4
  "status.initialized": "Buffer Modified: initialized for %{path}",
5
5
  "status.cleared_on_save": "Buffer Modified: cleared on save"
6
6
  },
7
- "es": {
8
- "status.loaded": "Plugin Buffer Modificado cargado",
9
- "status.initialized": "Buffer Modificado: inicializado para %{path}",
10
- "status.cleared_on_save": "Buffer Modificado: limpiado al guardar"
7
+ "cs": {
8
+ "status.loaded": "Plugin Buffer Modified nacten",
9
+ "status.initialized": "Buffer Modified: inicializovano pro %{path}",
10
+ "status.cleared_on_save": "Buffer Modified: vymazano pri ulozeni"
11
11
  },
12
12
  "de": {
13
13
  "status.loaded": "Buffer Modified Plugin geladen",
14
14
  "status.initialized": "Buffer Modified: initialisiert für %{path}",
15
15
  "status.cleared_on_save": "Buffer Modified: beim Speichern gelöscht"
16
16
  },
17
+ "es": {
18
+ "status.loaded": "Plugin Buffer Modificado cargado",
19
+ "status.initialized": "Buffer Modificado: inicializado para %{path}",
20
+ "status.cleared_on_save": "Buffer Modificado: limpiado al guardar"
21
+ },
17
22
  "fr": {
18
23
  "status.loaded": "Plugin Buffer Modified chargé",
19
24
  "status.initialized": "Buffer Modified: initialisé pour %{path}",
20
25
  "status.cleared_on_save": "Buffer Modified: effacé lors de la sauvegarde"
21
26
  },
27
+ "it": {
28
+ "status.loaded": "Plugin Buffer Modified caricato",
29
+ "status.initialized": "Buffer Modified: inizializzato per %{path}",
30
+ "status.cleared_on_save": "Buffer Modified: cancellato al salvataggio"
31
+ },
22
32
  "ja": {
23
33
  "status.loaded": "Buffer Modifiedプラグインを読み込みました",
24
34
  "status.initialized": "Buffer Modified: %{path}を初期化しました",
25
35
  "status.cleared_on_save": "Buffer Modified: 保存時にクリアしました"
26
36
  },
37
+ "ko": {
38
+ "status.loaded": "Buffer Modified 플러그인이 로드되었습니다",
39
+ "status.initialized": "Buffer Modified: %{path} 초기화됨",
40
+ "status.cleared_on_save": "Buffer Modified: 저장 시 지워짐"
41
+ },
42
+ "pt-BR": {
43
+ "status.loaded": "Plugin Buffer Modified carregado",
44
+ "status.initialized": "Buffer Modified: inicializado para %{path}",
45
+ "status.cleared_on_save": "Buffer Modified: limpo ao salvar"
46
+ },
47
+ "ru": {
48
+ "status.loaded": "Плагин Buffer Modified загружен",
49
+ "status.initialized": "Buffer Modified: инициализирован для %{path}",
50
+ "status.cleared_on_save": "Buffer Modified: очищен при сохранении"
51
+ },
52
+ "th": {
53
+ "status.loaded": "โหลดปลั๊กอิน Buffer Modified แล้ว",
54
+ "status.initialized": "Buffer Modified: เริ่มต้นสำหรับ %{path}",
55
+ "status.cleared_on_save": "Buffer Modified: ล้างเมื่อบันทึก"
56
+ },
57
+ "uk": {
58
+ "status.loaded": "Плагін Buffer Modified завантажено",
59
+ "status.initialized": "Buffer Modified: ініціалізовано для %{path}",
60
+ "status.cleared_on_save": "Buffer Modified: очищено при збереженні"
61
+ },
27
62
  "zh-CN": {
28
63
  "status.loaded": "Buffer Modified插件已加载",
29
64
  "status.initialized": "Buffer Modified: 已为%{path}初始化",