@happy-nut/monacori 0.1.25 → 0.1.26

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.
@@ -918,10 +918,18 @@ function allQuickItems() {
918
918
  }));
919
919
  }
920
920
 
921
+ // The agent's plan file, pinned to the top of Recent so a freshly-written plan is one ⌘E away — loadRecent
922
+ // only tracks files you've opened, so a brand-new plan would otherwise be absent until first opened.
923
+ var PLAN_PATH = '.monacori/plan.md';
924
+ function planQuickItem() {
925
+ var f = sourceByPath.get(PLAN_PATH);
926
+ if (!f) return null;
927
+ return { path: f.path, name: baseName(f.path), detail: 'plan', kind: 'source', recent: true, recentRank: -1 };
928
+ }
921
929
  function recentItems() {
922
930
  const all = allQuickItems();
923
931
  const byPath = new Map(all.map((item) => [item.path, item]));
924
- return loadRecent()
932
+ const items = loadRecent()
925
933
  .map((item) => byPath.get(item.path) || {
926
934
  path: item.path,
927
935
  name: baseName(item.path),
@@ -931,6 +939,9 @@ function recentItems() {
931
939
  recentRank: 0,
932
940
  })
933
941
  .map((item, index) => ({ ...item, recent: true, recentRank: index }));
942
+ const plan = planQuickItem();
943
+ if (plan) return [plan].concat(items.filter((it) => it.path !== plan.path));
944
+ return items;
934
945
  }
935
946
 
936
947
  function scoreQuickItem(item, query) {
@@ -1184,6 +1195,7 @@ document.addEventListener('keydown', (event) => {
1184
1195
  toggleHistory();
1185
1196
  return;
1186
1197
  }
1198
+ if (typeof isHistoryOpen === 'function' && isHistoryOpen() && typeof handleHistoryKey === 'function' && handleHistoryKey(event)) return;
1187
1199
 
1188
1200
  // Settings overlay (or a focused merged/memo dock) captures keys: stand down the rest of the global
1189
1201
  // shortcuts (Cmd+1, F7, Cmd+[/], Cmd+B, …). Each has its own Esc + editing handlers.
@@ -1646,7 +1658,6 @@ window.addEventListener('beforeunload', saveUiState);
1646
1658
  });
1647
1659
  ensureDiffCursor();
1648
1660
  })();
1649
-
1650
1661
  // ===== Side-by-side diff caret (keyboard navigation across the old/new panes) =====
1651
1662
  function isDiffViewVisible() {
1652
1663
  var d = document.getElementById('diff-view');
@@ -2088,6 +2099,19 @@ function commentsAt(path, line) {
2088
2099
  function commentKindLabel(kind) {
2089
2100
  return kind === 'q' ? t('comment.kind.q') : t('comment.kind.c');
2090
2101
  }
2102
+ // Monochrome inline icons for the two comment kinds: a help-circle for questions, a pencil for change
2103
+ // requests (the pencil path matches the activity-rail "c" button). No emoji, no color — stroke=currentColor
2104
+ // so the kind pill stays monotone (.mc-kind in viewer.css); the icon, not the color, distinguishes q vs c.
2105
+ function commentKindIcon(kind) {
2106
+ var path = kind === 'q'
2107
+ ? '<circle cx="12" cy="12" r="9"/><path d="M9.4 9.3a2.7 2.7 0 0 1 5.2 1c0 1.8-2.6 2.4-2.6 2.4"/><line x1="12" y1="16.7" x2="12.01" y2="16.7"/>'
2108
+ : '<path d="M14.5 5.5l4 4"/><path d="M4.5 19.5l1-4 10-10 3 3-10 10z"/>';
2109
+ return '<svg class="mc-kind-ic" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + path + '</svg>';
2110
+ }
2111
+ // Full inner HTML for a .mc-kind pill: monochrome icon + the (localized) label.
2112
+ function commentKindHtml(kind) {
2113
+ return commentKindIcon(kind) + '<span class="mc-kind-text">' + escapeHtml(commentKindLabel(kind)) + '</span>';
2114
+ }
2091
2115
  function relevantLines(path) {
2092
2116
  var set = {};
2093
2117
  reviewComments.forEach(function (c) { if (c.path === path) set[c.line] = true; });
@@ -2191,14 +2215,14 @@ function threadHtml(path, line) {
2191
2215
  commentsAt(path, line).forEach(function (c) {
2192
2216
  if (composerState && composerState.editSeq === c.seq) return; // being edited -> rendered as the composer below
2193
2217
  html += '<div class="mc-card mc-' + c.kind + '">'
2194
- + '<div class="mc-card-head"><span class="mc-kind">' + commentKindLabel(c.kind) + '</span>'
2218
+ + '<div class="mc-card-head"><span class="mc-kind">' + commentKindHtml(c.kind) + '</span>'
2195
2219
  + '<button type="button" class="mc-del" data-seq="' + c.seq + '" title="' + escapeHtml(t('composer.delete')) + '">×</button></div>'
2196
2220
  + '<div class="mc-card-body">' + escapeHtml(c.text) + '</div></div>';
2197
2221
  });
2198
2222
  if (composerState && composerState.path === path && composerState.line === line) {
2199
2223
  var ph = composerState.kind === 'q' ? t('composer.question') : t('composer.changeRequest');
2200
2224
  html += '<div class="mc-card mc-' + composerState.kind + ' mc-composer">'
2201
- + '<div class="mc-card-head"><span class="mc-kind">' + commentKindLabel(composerState.kind) + '</span><span class="mc-target" title="' + escapeHtml(composerState.path || '') + '">' + escapeHtml(composerTargetLabel(composerState)) + '</span></div>'
2225
+ + '<div class="mc-card-head"><span class="mc-kind">' + commentKindHtml(composerState.kind) + '</span><span class="mc-target" title="' + escapeHtml(composerState.path || '') + '">' + escapeHtml(composerTargetLabel(composerState)) + '</span></div>'
2202
2226
  + '<textarea class="mc-input" rows="3" placeholder="' + escapeHtml(ph) + '">' + escapeHtml(composerState.editText || '') + '</textarea>'
2203
2227
  + '<div class="mc-actions"><button type="button" class="mc-btn mc-save">' + escapeHtml(t('composer.save')) + '</button>'
2204
2228
  + '<button type="button" class="mc-btn mc-ghost mc-cancel">' + escapeHtml(t('composer.cancel')) + '</button>'
@@ -2390,7 +2414,7 @@ function saveComposer(ta) {
2390
2414
  // Settings → Merge prompts (stored per browser in localStorage); buildMergedText + the textarea
2391
2415
  // placeholders fall back to these when the stored value is empty.
2392
2416
  function defaultMergePrompt(kind) {
2393
- return t(kind === 'q' ? 'mergePrompt.default.q' : 'mergePrompt.default.c');
2417
+ return t(kind === 'q' ? 'mergePrompt.default.q' : kind === 'plan' ? 'plan.contract' : 'mergePrompt.default.c');
2394
2418
  }
2395
2419
  var mergePromptsKey = 'monacori-merge-prompts';
2396
2420
  function loadMergePrompts() {
@@ -2511,6 +2535,10 @@ function buildMergedText(kind) {
2511
2535
  var items = reviewComments.filter(function (c) { return c.kind === kind; });
2512
2536
  var nl = String.fromCharCode(10);
2513
2537
  var lines = [];
2538
+ // Change requests are task instructions, so they lead with the plan contract: plan first, decompose into
2539
+ // verifiable steps, write the plan to .monacori/plan.md (editable in Settings → Merge prompts). Questions
2540
+ // are read-only clarifications, so they skip it.
2541
+ if (kind === 'c') { lines.push(mergePromptFor('plan')); lines.push(''); }
2514
2542
  // Per-kind agent contract heading (editable in Settings → Merge prompts; default otherwise).
2515
2543
  lines.push(mergePromptFor(kind));
2516
2544
  lines.push('');
@@ -2751,9 +2779,13 @@ function openMemoView() {
2751
2779
  sendBtn.setAttribute('data-i18n', 'merged.sendToTerminal');
2752
2780
  sendBtn.textContent = t('merged.sendToTerminal');
2753
2781
  sendBtn.addEventListener('click', function () {
2782
+ // The memo is a task instruction, so it leads with the plan contract (plan first, decompose into
2783
+ // verifiable steps, write the plan to .monacori/plan.md) — same prefix change requests get. Empty memo
2784
+ // sends nothing extra.
2754
2785
  var text = area.value;
2786
+ var planned = text.trim() ? mergePromptFor('plan') + '\n\n' + text : text;
2755
2787
  dock.close();
2756
- window.__monacoriTerminal.enterSendMode(text);
2788
+ window.__monacoriTerminal.enterSendMode(planned);
2757
2789
  });
2758
2790
  dock.bar.insertBefore(sendBtn, dock.bar.querySelector('.dock-max'));
2759
2791
  }
@@ -3211,6 +3243,7 @@ if (window.monacoriMenu && typeof window.monacoriMenu.onCloseTab === 'function')
3211
3243
  var gearBtn = document.getElementById('app-info-btn');
3212
3244
  var flag = document.getElementById('app-update-flag');
3213
3245
  var updateBtn = document.getElementById('app-info-update');
3246
+ var pta = document.getElementById('settings-prompt-plan');
3214
3247
  var qta = document.getElementById('settings-prompt-q');
3215
3248
  var cta = document.getElementById('settings-prompt-c');
3216
3249
  var resetBtn = document.getElementById('settings-reset');
@@ -3223,6 +3256,7 @@ if (window.monacoriMenu && typeof window.monacoriMenu.onCloseTab === 'function')
3223
3256
  }
3224
3257
  function fill() {
3225
3258
  var s = loadMergePrompts();
3259
+ if (pta) { pta.value = typeof s.plan === 'string' ? s.plan : ''; pta.placeholder = defaultMergePrompt('plan'); }
3226
3260
  if (qta) { qta.value = typeof s.q === 'string' ? s.q : ''; qta.placeholder = defaultMergePrompt('q'); }
3227
3261
  if (cta) { cta.value = typeof s.c === 'string' ? s.c : ''; cta.placeholder = defaultMergePrompt('c'); }
3228
3262
  }
@@ -3258,9 +3292,10 @@ if (window.monacoriMenu && typeof window.monacoriMenu.onCloseTab === 'function')
3258
3292
  }).catch(function () { updateBtn.disabled = false; if (status) status.textContent = t('settings.updateFailed'); });
3259
3293
  });
3260
3294
  }
3295
+ if (pta) pta.addEventListener('input', function () { saveMergePrompt('plan', pta.value); flash(); });
3261
3296
  if (qta) qta.addEventListener('input', function () { saveMergePrompt('q', qta.value); flash(); });
3262
3297
  if (cta) cta.addEventListener('input', function () { saveMergePrompt('c', cta.value); flash(); });
3263
- if (resetBtn) resetBtn.addEventListener('click', function () { saveMergePrompt('q', ''); saveMergePrompt('c', ''); fill(); flash(); });
3298
+ if (resetBtn) resetBtn.addEventListener('click', function () { saveMergePrompt('plan', ''); saveMergePrompt('q', ''); saveMergePrompt('c', ''); fill(); flash(); });
3264
3299
  // Terminal-bell notification toggle (default ON — persistRead returns undefined when never set).
3265
3300
  var bellCb = document.getElementById('set-bell-notify');
3266
3301
  if (bellCb) {
@@ -5313,6 +5348,8 @@ var historyGraph = [];
5313
5348
  var historyMaxLane = 0;
5314
5349
  var historyActiveSha = '';
5315
5350
  var historyLoading = false;
5351
+ var historyFocus = 'commits'; // commits | files | diff
5352
+ var historyDiffState = null;
5316
5353
 
5317
5354
  // Lane layout. Walks commits newest-first, tracking open edges (lanes) by the hash each expects next.
5318
5355
  // Returns per-row { hash, myLane, color, topEdges, bottomEdges } using LANE INDICES + COLOR INDICES (px-free,
@@ -5426,6 +5463,33 @@ function renderHistoryList() {
5426
5463
  }).join('');
5427
5464
  }
5428
5465
 
5466
+ function historyVisibleRows() {
5467
+ var list = document.getElementById('history-list');
5468
+ return list ? Array.prototype.slice.call(list.querySelectorAll('.hrow')).filter(function (r) { return !r.classList.contains('hidden'); }) : [];
5469
+ }
5470
+ function selectHistoryCommit(sha, shouldScroll) {
5471
+ if (!sha) return;
5472
+ historyActiveSha = sha;
5473
+ var list = document.getElementById('history-list');
5474
+ if (!list) return;
5475
+ var active = null;
5476
+ list.querySelectorAll('.hrow').forEach(function (r) {
5477
+ var on = r.dataset.sha === sha;
5478
+ r.classList.toggle('active', on);
5479
+ if (on) active = r;
5480
+ });
5481
+ if (shouldScroll !== false && active && active.scrollIntoView) active.scrollIntoView({ block: 'nearest' });
5482
+ }
5483
+ function moveHistoryCommit(delta) {
5484
+ var rows = historyVisibleRows();
5485
+ if (!rows.length) return;
5486
+ var idx = rows.findIndex(function (r) { return r.dataset.sha === historyActiveSha; });
5487
+ if (idx < 0) idx = delta > 0 ? -1 : 0;
5488
+ idx = Math.max(0, Math.min(rows.length - 1, idx + delta));
5489
+ historyFocus = 'commits';
5490
+ selectHistoryCommit(rows[idx].dataset.sha, true);
5491
+ }
5492
+
5429
5493
  // Text filter (subject / author). The graph only reads right on the full contiguous history, so filtering
5430
5494
  // hides the graph column (IntelliJ does the same) and just shows matching rows.
5431
5495
  function applyHistoryFilter() {
@@ -5440,13 +5504,15 @@ function applyHistoryFilter() {
5440
5504
  var hit = !q || (c.subject + '\n' + c.author + '\n' + c.hash).toLowerCase().indexOf(q) !== -1;
5441
5505
  rows[i].classList.toggle('hidden', !hit);
5442
5506
  }
5507
+ var visible = historyVisibleRows();
5508
+ if (visible.length && !visible.some(function (r) { return r.dataset.sha === historyActiveSha; })) {
5509
+ selectHistoryCommit(visible[0].dataset.sha, false);
5510
+ }
5443
5511
  }
5444
5512
 
5445
5513
  function openHistoryCommit(sha) {
5446
5514
  if (!sha || !window.monacoriGit) return;
5447
- historyActiveSha = sha;
5448
- var list = document.getElementById('history-list');
5449
- if (list) list.querySelectorAll('.hrow').forEach(function (r) { r.classList.toggle('active', r.dataset.sha === sha); });
5515
+ selectHistoryCommit(sha, true);
5450
5516
  var detail = document.getElementById('history-detail');
5451
5517
  if (detail) detail.innerHTML = '<div class="quick-open-empty">' + escapeHtml(t('history.loading')) + '</div>';
5452
5518
  Promise.resolve(window.monacoriGit.commitDiff(sha)).then(function (d) {
@@ -5465,9 +5531,335 @@ function renderHistoryDetail(d) {
5465
5531
  + '<span class="hd-date">' + escapeHtml(historyShortDate(d.date)) + '</span>'
5466
5532
  + historyRefBadges(d.refs) + '</div></div>';
5467
5533
  var body = (d.diffHtml && d.diffHtml.trim())
5468
- ? '<div class="history-diff diff2html-container">' + d.diffHtml + '</div>'
5534
+ ? '<div class="history-workspace"><aside id="history-files" class="history-files"></aside><div id="history-diff-container" class="history-diff diff2html-container" tabindex="0" aria-readonly="true">' + d.diffHtml + '</div></div>'
5469
5535
  : '<div class="quick-open-empty">' + escapeHtml(t(d.isMerge ? 'history.merge' : 'history.noDiff')) + '</div>';
5470
5536
  detail.innerHTML = head + body;
5537
+ setupHistoryDiffWorkspace(d.hash || historyActiveSha);
5538
+ }
5539
+
5540
+ function historyWrapperPathKey(w) {
5541
+ return (w.dataset && w.dataset.path) || ((w.querySelector('.d2h-file-name') || {}).textContent || '').trim();
5542
+ }
5543
+ function historyWrapperByPath(path) {
5544
+ if (!historyDiffState || !path) return null;
5545
+ for (var i = 0; i < historyDiffState.wrappers.length; i++) {
5546
+ if (historyWrapperPathKey(historyDiffState.wrappers[i]) === path) return historyDiffState.wrappers[i];
5547
+ }
5548
+ return null;
5549
+ }
5550
+ function historySideTables(wrapper) {
5551
+ var sides = wrapper ? wrapper.querySelectorAll('.d2h-file-side-diff') : [];
5552
+ return { left: sides[0] || null, right: sides[sides.length - 1] || null };
5553
+ }
5554
+ function historySideTable(wrapper, side) {
5555
+ var t = historySideTables(wrapper);
5556
+ return side === 'old' ? t.left : t.right;
5557
+ }
5558
+ function historyRowsOf(sideTable) {
5559
+ return diffRowsOf(sideTable);
5560
+ }
5561
+ function historyRowAt(wrapper, side, rowIndex) {
5562
+ return historyRowsOf(historySideTable(wrapper, side))[rowIndex] || null;
5563
+ }
5564
+ function historyDiffRowInfoFromNode(node) {
5565
+ var el = node ? (node.nodeType === 1 ? node : node.parentElement) : null;
5566
+ if (!el || !el.closest) return null;
5567
+ var wrapper = el.closest('.d2h-file-wrapper');
5568
+ var sideEl = el.closest('.d2h-file-side-diff');
5569
+ var row = el.closest('tr');
5570
+ if (!wrapper || !sideEl || !row || !isDiffCodeRow(row)) return null;
5571
+ var path = historyWrapperPathKey(wrapper);
5572
+ var t = historySideTables(wrapper);
5573
+ var rowIndex = historyRowsOf(sideEl).indexOf(row);
5574
+ if (!path || rowIndex < 0) return null;
5575
+ return { path: path, side: sideEl === t.left ? 'old' : 'new', rowIndex: rowIndex };
5576
+ }
5577
+ function historyFirstDiffCodeRow(wrapper, side) {
5578
+ var rows = historyRowsOf(historySideTable(wrapper, side));
5579
+ for (var i = 0; i < rows.length; i++) if (isDiffCodeRow(rows[i])) return i;
5580
+ return -1;
5581
+ }
5582
+ function historyFirstCodeRowOfHunk(hunkRow) {
5583
+ var row = hunkRow.nextElementSibling;
5584
+ var firstRow = null;
5585
+ while (row && !row.classList.contains('history-hunk') && !row.classList.contains('history-hunk-peer')) {
5586
+ if (row.querySelector && row.querySelector('.d2h-code-side-line')) {
5587
+ if (!firstRow) firstRow = row;
5588
+ if (isChangeCodeRow(row)) return row;
5589
+ }
5590
+ row = row.nextElementSibling;
5591
+ }
5592
+ return firstRow || hunkRow;
5593
+ }
5594
+ function historyFirstChangeRowForCaret(hunkRow) {
5595
+ var wrapper = hunkRow.closest('.d2h-file-wrapper');
5596
+ var sides = wrapper ? wrapper.querySelectorAll('.d2h-file-side-diff') : [];
5597
+ var hunkSideEl = hunkRow.closest('.d2h-file-side-diff');
5598
+ if (sides.length >= 2 && hunkSideEl) {
5599
+ var hunkRows = Array.prototype.slice.call(hunkSideEl.querySelectorAll('tr'));
5600
+ var otherEl = hunkSideEl === sides[0] ? sides[1] : sides[0];
5601
+ var otherRows = Array.prototype.slice.call(otherEl.querySelectorAll('tr'));
5602
+ var fallbackOld = null;
5603
+ for (var i = hunkRows.indexOf(hunkRow) + 1; i < hunkRows.length; i++) {
5604
+ var hr = hunkRows[i];
5605
+ if (hr.classList.contains('history-hunk') || hr.classList.contains('history-hunk-peer')) break;
5606
+ if (isChangeCodeRow(otherRows[i])) return otherRows[i];
5607
+ if (fallbackOld === null && isChangeCodeRow(hr)) fallbackOld = hr;
5608
+ }
5609
+ if (fallbackOld) return fallbackOld;
5610
+ }
5611
+ return historyFirstCodeRowOfHunk(hunkRow);
5612
+ }
5613
+ function setupHistoryDiffWorkspace(sha) {
5614
+ var container = document.getElementById('history-diff-container');
5615
+ var filesEl = document.getElementById('history-files');
5616
+ if (!container || !filesEl) { historyDiffState = null; return; }
5617
+ container.querySelectorAll('.d2h-code-side-linenumber, .d2h-code-linenumber, .d2h-code-line-prefix').forEach(function (el) { el.setAttribute('contenteditable', 'false'); });
5618
+ var wrappers = Array.prototype.slice.call(container.querySelectorAll('.d2h-file-wrapper'));
5619
+ var files = [], hunks = [];
5620
+ var hunkIndex = 0;
5621
+ wrappers.forEach(function (wrapper, fileIndex) {
5622
+ var path = historyWrapperPathKey(wrapper);
5623
+ if (path) wrapper.dataset.path = path;
5624
+ wrapper.dataset.historyFileIndex = String(fileIndex);
5625
+ var first = hunkIndex;
5626
+ var headerToIndex = new Map();
5627
+ Array.prototype.forEach.call(wrapper.querySelectorAll('tr'), function (row) {
5628
+ var header = (row.textContent || '').trim();
5629
+ if (header.indexOf('@@') !== 0) return;
5630
+ var idx = headerToIndex.get(header);
5631
+ if (idx === undefined) {
5632
+ idx = hunkIndex++;
5633
+ headerToIndex.set(header, idx);
5634
+ row.classList.add('history-hunk');
5635
+ hunks[idx] = { path: path, row: row };
5636
+ } else {
5637
+ row.classList.add('history-hunk-peer');
5638
+ }
5639
+ row.dataset.historyHunkIndex = String(idx);
5640
+ row.dataset.historyFile = path;
5641
+ });
5642
+ files.push({ path: path, hunk: first, count: hunkIndex - first });
5643
+ });
5644
+ historyDiffState = { sha: sha, container: container, filesEl: filesEl, wrappers: wrappers, files: files, hunks: hunks, currentHunk: -1, cursor: null, fileFocusIndex: 0 };
5645
+ filesEl.innerHTML = files.map(function (file, i) {
5646
+ var slash = file.path.lastIndexOf('/');
5647
+ var name = slash >= 0 ? file.path.slice(slash + 1) : file.path;
5648
+ var dir = slash >= 0 ? file.path.slice(0, slash) : '';
5649
+ return '<button type="button" class="file-link history-file" data-index="' + i + '" data-file="' + escapeHtml(file.path) + '" data-hunk="' + file.hunk + '">'
5650
+ + '<span class="status status-modified">M</span><span class="change-name"><span class="path" title="' + escapeHtml(file.path) + '">' + escapeHtml(name) + '</span>'
5651
+ + (dir ? '<span class="change-dir">' + escapeHtml(dir) + '</span>' : '') + '</span></button>';
5652
+ }).join('');
5653
+ container.addEventListener('click', function (event) {
5654
+ var info = historyDiffRowInfoFromNode(event.target);
5655
+ if (info && info.path) {
5656
+ historyFocus = 'diff';
5657
+ historySetDiffCursor(info.path, info.side, info.rowIndex, 0, false);
5658
+ }
5659
+ });
5660
+ if (files[0]) historyShowFile(files[0].path, files[0].hunk, false);
5661
+ focusHistoryDiff();
5662
+ }
5663
+ function historySetFileFocus(index) {
5664
+ if (!historyDiffState || !historyDiffState.files.length) return;
5665
+ var max = historyDiffState.files.length - 1;
5666
+ historyDiffState.fileFocusIndex = Math.max(0, Math.min(max, index));
5667
+ historyFocus = 'files';
5668
+ var btns = historyDiffState.filesEl.querySelectorAll('.history-file');
5669
+ btns.forEach(function (b, i) {
5670
+ b.classList.toggle('tree-focus', i === historyDiffState.fileFocusIndex);
5671
+ if (i === historyDiffState.fileFocusIndex && b.scrollIntoView) b.scrollIntoView({ block: 'nearest' });
5672
+ });
5673
+ }
5674
+ function focusHistoryFiles() {
5675
+ if (!historyDiffState) return;
5676
+ var active = historyDiffState.files.findIndex(function (f) { return f.path === historyCurrentFile(); });
5677
+ historySetFileFocus(active >= 0 ? active : 0);
5678
+ }
5679
+ function focusHistoryDiff() {
5680
+ if (!historyDiffState) return;
5681
+ historyFocus = 'diff';
5682
+ historyDiffState.filesEl.querySelectorAll('.history-file').forEach(function (b) { b.classList.remove('tree-focus'); });
5683
+ try { historyDiffState.container.focus(); } catch (e) {}
5684
+ }
5685
+ function historyCurrentFile() {
5686
+ if (!historyDiffState) return '';
5687
+ var active = historyDiffState.filesEl.querySelector('.history-file.active');
5688
+ return active ? active.dataset.file || '' : '';
5689
+ }
5690
+ function historyShowFile(path, hunkIndex, shouldScroll) {
5691
+ if (!historyDiffState || !path) return;
5692
+ historyDiffState.wrappers.forEach(function (wrapper) {
5693
+ wrapper.classList.toggle('df-inactive', historyWrapperPathKey(wrapper) !== path);
5694
+ });
5695
+ historyDiffState.filesEl.querySelectorAll('.history-file').forEach(function (button, i) {
5696
+ var on = button.dataset.file === path;
5697
+ button.classList.toggle('active', on);
5698
+ if (on) historyDiffState.fileFocusIndex = i;
5699
+ });
5700
+ var file = historyDiffState.files.find(function (f) { return f.path === path; });
5701
+ var target = typeof hunkIndex === 'number' && hunkIndex >= 0 ? hunkIndex : (file ? file.hunk : -1);
5702
+ if (target >= 0 && historyDiffState.hunks[target]) historySetActiveHunk(target, shouldScroll !== false);
5703
+ else historyEnsureDiffCursor(path, shouldScroll !== false);
5704
+ }
5705
+ function historyHunkPathAt(index) {
5706
+ return historyDiffState && historyDiffState.hunks[index] ? historyDiffState.hunks[index].path : '';
5707
+ }
5708
+ function historySetActiveHunk(index, shouldScroll) {
5709
+ if (!historyDiffState || !historyDiffState.hunks.length) return;
5710
+ var len = historyDiffState.hunks.length;
5711
+ var idx = ((index % len) + len) % len;
5712
+ var h = historyDiffState.hunks[idx];
5713
+ if (!h || !h.path) return;
5714
+ historyDiffState.currentHunk = idx;
5715
+ historyDiffState.wrappers.forEach(function (wrapper) {
5716
+ wrapper.classList.toggle('df-inactive', historyWrapperPathKey(wrapper) !== h.path);
5717
+ });
5718
+ historyDiffState.filesEl.querySelectorAll('.history-file').forEach(function (button, i) {
5719
+ var on = button.dataset.file === h.path;
5720
+ button.classList.toggle('active', on);
5721
+ if (on) historyDiffState.fileFocusIndex = i;
5722
+ });
5723
+ historyDiffState.container.querySelectorAll('.history-hunk.active, .history-hunk-peer.active').forEach(function (row) { row.classList.remove('active'); });
5724
+ historyDiffState.container.querySelectorAll('[data-history-hunk-index="' + idx + '"]').forEach(function (row) { row.classList.add('active'); });
5725
+ var targetRow = historyFirstChangeRowForCaret(h.row);
5726
+ if (targetRow) {
5727
+ var info = historyDiffRowInfoFromNode(targetRow);
5728
+ if (info) historySetDiffCursor(info.path, info.side, info.rowIndex, 0, shouldScroll);
5729
+ }
5730
+ }
5731
+ function historyEnsureDiffCursor(path, reveal) {
5732
+ var wrapper = historyWrapperByPath(path);
5733
+ var ri = historyFirstDiffCodeRow(wrapper, 'new');
5734
+ if (ri >= 0) historySetDiffCursor(path, 'new', ri, 0, reveal);
5735
+ }
5736
+ function historyClearDiffCaret() {
5737
+ if (!historyDiffState) return;
5738
+ historyDiffState.container.querySelectorAll('.mc-diff-cursor-row').forEach(function (r) { r.classList.remove('mc-diff-cursor-row'); });
5739
+ historyDiffState.container.querySelectorAll('.code-cursor').forEach(function (s) { var p = s.parentNode; if (p) { p.removeChild(s); if (p.normalize) p.normalize(); } });
5740
+ }
5741
+ function historyRenderDiffCaret() {
5742
+ historyClearDiffCaret();
5743
+ if (!historyDiffState || !historyDiffState.cursor) return;
5744
+ var c = historyDiffState.cursor;
5745
+ var wrapper = historyWrapperByPath(c.path);
5746
+ var row = wrapper ? historyRowAt(wrapper, c.side, c.rowIndex) : null;
5747
+ if (!row) return;
5748
+ row.classList.add('mc-diff-cursor-row');
5749
+ var ctn = diffCellCtn(row);
5750
+ if (!ctn) return;
5751
+ if ((ctn.textContent || '').length === 0) {
5752
+ var emptySpan = document.createElement('span');
5753
+ emptySpan.className = 'code-cursor';
5754
+ emptySpan.setAttribute('aria-hidden', 'true');
5755
+ emptySpan.style.position = 'absolute';
5756
+ ctn.appendChild(emptySpan);
5757
+ return;
5758
+ }
5759
+ var pos = diffCaretDomPosition(ctn, c.column);
5760
+ if (!pos) return;
5761
+ var span = document.createElement('span');
5762
+ span.className = 'code-cursor';
5763
+ span.setAttribute('aria-hidden', 'true');
5764
+ try {
5765
+ var off = pos.node.nodeType === 3 ? Math.min(pos.offset, (pos.node.textContent || '').length) : pos.offset;
5766
+ var range = document.createRange();
5767
+ range.setStart(pos.node, off);
5768
+ range.collapse(true);
5769
+ range.insertNode(span);
5770
+ } catch (e) {}
5771
+ }
5772
+ function historySetDiffCursor(path, side, rowIndex, column, reveal) {
5773
+ if (!historyDiffState) return;
5774
+ var wrapper = historyWrapperByPath(path);
5775
+ if (!wrapper) return;
5776
+ var rows = historyRowsOf(historySideTable(wrapper, side));
5777
+ if (!rows.length) return;
5778
+ var ri = Math.max(0, Math.min(rowIndex, rows.length - 1));
5779
+ var col = Math.max(0, Math.min(column, diffLineText(rows[ri]).length));
5780
+ historyDiffState.cursor = { path: path, side: side, rowIndex: ri, column: col };
5781
+ historyRenderDiffCaret();
5782
+ if (reveal) scrolloffReveal(rows[ri], historyDiffState.container, 0.15);
5783
+ }
5784
+ function historyMoveDiffCursor(dLine, dColumn) {
5785
+ if (!historyDiffState || !historyDiffState.cursor) return;
5786
+ var c = historyDiffState.cursor;
5787
+ var wrapper = historyWrapperByPath(c.path);
5788
+ if (!wrapper) return;
5789
+ var rows = historyRowsOf(historySideTable(wrapper, c.side));
5790
+ var ri = c.rowIndex, col = c.column;
5791
+ var text = diffLineText(rows[ri]);
5792
+ if (dColumn < 0) {
5793
+ if (col > 0) col -= 1;
5794
+ else { var p = ri - 1; while (p >= 0 && !isDiffCodeRow(rows[p])) p -= 1; if (p >= 0) { ri = p; col = diffLineText(rows[p]).length; } }
5795
+ } else if (dColumn > 0) {
5796
+ if (col < text.length) col += 1;
5797
+ else { var n = ri + 1; while (n < rows.length && !isDiffCodeRow(rows[n])) n += 1; if (n < rows.length) { ri = n; col = 0; } }
5798
+ }
5799
+ if (dLine !== 0) {
5800
+ var step = dLine > 0 ? 1 : -1;
5801
+ var cand = ri + step;
5802
+ while (cand >= 0 && cand < rows.length && !isDiffCodeRow(rows[cand])) cand += step;
5803
+ if (cand >= 0 && cand < rows.length) { ri = cand; col = Math.min(col, diffLineText(rows[ri]).length); }
5804
+ }
5805
+ historySetDiffCursor(c.path, c.side, ri, col, true);
5806
+ }
5807
+ function historyNextHunk(delta) {
5808
+ if (!historyDiffState || !historyDiffState.hunks.length) return;
5809
+ var base = historyDiffState.currentHunk >= 0 ? historyDiffState.currentHunk : 0;
5810
+ historySetActiveHunk(base + delta, true);
5811
+ focusHistoryDiff();
5812
+ }
5813
+ function historySelectAllDiff() {
5814
+ if (!historyDiffState) return;
5815
+ var sel = window.getSelection();
5816
+ if (!sel) return;
5817
+ var range = document.createRange();
5818
+ range.selectNodeContents(historyDiffState.container);
5819
+ sel.removeAllRanges();
5820
+ sel.addRange(range);
5821
+ }
5822
+ function handleHistoryDiffKey(event) {
5823
+ if (!historyDiffState || !historyDiffState.cursor) return false;
5824
+ if (event.key === 'Tab' && event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey) {
5825
+ var tc = historyDiffState.cursor;
5826
+ var tw = historyWrapperByPath(tc.path);
5827
+ var otherSide = tc.side === 'new' ? 'old' : 'new';
5828
+ var otherRow = tw ? historyRowAt(tw, otherSide, tc.rowIndex) : null;
5829
+ if (isDiffCodeRow(otherRow)) historySetDiffCursor(tc.path, otherSide, tc.rowIndex, Math.min(tc.column, diffLineText(otherRow).length), true);
5830
+ return true;
5831
+ }
5832
+ if (!event.metaKey && !event.ctrlKey && !event.altKey) {
5833
+ if (event.key === 'ArrowDown') { historyMoveDiffCursor(1, 0); return true; }
5834
+ if (event.key === 'ArrowUp') { historyMoveDiffCursor(-1, 0); return true; }
5835
+ if (event.key === 'ArrowLeft') { historyMoveDiffCursor(0, -1); return true; }
5836
+ if (event.key === 'ArrowRight') { historyMoveDiffCursor(0, 1); return true; }
5837
+ }
5838
+ if (event.altKey && !event.metaKey && !event.ctrlKey && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
5839
+ var wc = historyDiffState.cursor;
5840
+ var ww = historyWrapperByPath(wc.path);
5841
+ var wr = ww ? historyRowAt(ww, wc.side, wc.rowIndex) : null;
5842
+ var nextCol = typeof nextWordBoundary === 'function'
5843
+ ? nextWordBoundary(diffLineText(wr), wc.column, event.key === 'ArrowRight' ? 1 : -1)
5844
+ : wc.column;
5845
+ historySetDiffCursor(wc.path, wc.side, wc.rowIndex, nextCol, true);
5846
+ return true;
5847
+ }
5848
+ if ((event.metaKey || event.ctrlKey) && !event.altKey && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
5849
+ var c = historyDiffState.cursor;
5850
+ var wrapper = historyWrapperByPath(c.path);
5851
+ var row = wrapper ? historyRowAt(wrapper, c.side, c.rowIndex) : null;
5852
+ var len = diffLineText(row).length;
5853
+ if (event.key === 'ArrowLeft') {
5854
+ if (c.column > 0) historySetDiffCursor(c.path, c.side, c.rowIndex, 0, true);
5855
+ else if (c.side === 'new') { var oldRow = historyRowAt(wrapper, 'old', c.rowIndex); if (isDiffCodeRow(oldRow)) historySetDiffCursor(c.path, 'old', c.rowIndex, diffLineText(oldRow).length, true); }
5856
+ } else {
5857
+ if (c.column < len) historySetDiffCursor(c.path, c.side, c.rowIndex, len, true);
5858
+ else if (c.side === 'old') { var newRow = historyRowAt(wrapper, 'new', c.rowIndex); if (isDiffCodeRow(newRow)) historySetDiffCursor(c.path, 'new', c.rowIndex, 0, true); }
5859
+ }
5860
+ return true;
5861
+ }
5862
+ return false;
5471
5863
  }
5472
5864
 
5473
5865
  function isHistoryOpen() {
@@ -5477,6 +5869,7 @@ function isHistoryOpen() {
5477
5869
  function closeHistory() {
5478
5870
  var v = document.getElementById('history-view');
5479
5871
  if (v) v.classList.add('hidden');
5872
+ historyFocus = 'commits';
5480
5873
  if (typeof syncRail === 'function') syncRail();
5481
5874
  }
5482
5875
  function openHistory() {
@@ -5489,6 +5882,8 @@ function openHistory() {
5489
5882
  if (search) { search.value = ''; }
5490
5883
  applyHistoryFilter();
5491
5884
  historyLoading = true;
5885
+ historyFocus = 'commits';
5886
+ historyDiffState = null;
5492
5887
  renderHistoryList();
5493
5888
  Promise.resolve(window.monacoriGit.log({ limit: 300 })).then(function (commits) {
5494
5889
  historyLoading = false;
@@ -5498,13 +5893,64 @@ function openHistory() {
5498
5893
  renderHistoryList();
5499
5894
  var detail = document.getElementById('history-detail');
5500
5895
  if (detail) detail.innerHTML = '<div class="quick-open-empty">' + escapeHtml(t('history.selectCommit')) + '</div>';
5501
- if (historyCommits[0]) openHistoryCommit(historyCommits[0].hash); // preview the newest commit
5502
- if (search) setTimeout(function () { try { search.focus(); } catch (e) {} }, 0);
5896
+ if (historyCommits[0]) selectHistoryCommit(historyCommits[0].hash, false);
5897
+ setTimeout(function () { try { v.focus(); } catch (e) {} }, 0);
5503
5898
  }, function () { historyLoading = false; renderHistoryList(); });
5504
5899
  }
5505
5900
  function toggleHistory() { if (isHistoryOpen()) closeHistory(); else openHistory(); }
5506
5901
  if (typeof window !== 'undefined') window.__monacoriHistory = { open: openHistory, close: closeHistory, toggle: toggleHistory, isOpen: isHistoryOpen };
5507
5902
 
5903
+ function handleHistoryKey(e) {
5904
+ if (!isHistoryOpen()) return false;
5905
+ var ae = document.activeElement;
5906
+ var inSearch = ae && ae.id === 'history-search';
5907
+ if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey && (e.code === 'Digit9' || e.key === '9')) {
5908
+ e.preventDefault(); e.stopPropagation(); closeHistory(); return true;
5909
+ }
5910
+ if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeHistory(); return true; }
5911
+ if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey && e.key === '0') {
5912
+ if (historyDiffState) { e.preventDefault(); e.stopPropagation(); focusHistoryFiles(); return true; }
5913
+ }
5914
+ if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey && (e.key === 'a' || e.key === 'A') && historyFocus === 'diff') {
5915
+ e.preventDefault(); e.stopPropagation(); historySelectAllDiff(); return true;
5916
+ }
5917
+ if (e.key === 'PageDown' || e.key === 'PageUp') {
5918
+ var scroller = historyFocus === 'diff' && historyDiffState ? historyDiffState.container : document.getElementById('history-list');
5919
+ if (scroller) { e.preventDefault(); e.stopPropagation(); scroller.scrollTop += (e.key === 'PageDown' ? 0.9 : -0.9) * scroller.clientHeight; return true; }
5920
+ }
5921
+ if (e.key === 'F7' && !e.metaKey && !e.ctrlKey && !e.altKey) {
5922
+ e.preventDefault(); e.stopPropagation(); historyNextHunk(e.shiftKey ? -1 : 1); return true;
5923
+ }
5924
+ if (!e.metaKey && !e.ctrlKey && !e.altKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
5925
+ e.preventDefault(); e.stopPropagation();
5926
+ var delta = e.key === 'ArrowDown' ? 1 : -1;
5927
+ if (historyFocus === 'files') historySetFileFocus((historyDiffState ? historyDiffState.fileFocusIndex : 0) + delta);
5928
+ else if (historyFocus === 'diff' && historyDiffState) historyMoveDiffCursor(delta, 0);
5929
+ else moveHistoryCommit(delta);
5930
+ return true;
5931
+ }
5932
+ if (!e.metaKey && !e.ctrlKey && !e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight') && historyFocus === 'diff') {
5933
+ e.preventDefault(); e.stopPropagation(); historyMoveDiffCursor(0, e.key === 'ArrowRight' ? 1 : -1); return true;
5934
+ }
5935
+ if (!e.metaKey && !e.ctrlKey && !e.altKey && e.key === 'Enter') {
5936
+ e.preventDefault(); e.stopPropagation();
5937
+ if (historyFocus === 'diff') {
5938
+ return true;
5939
+ } else if (historyFocus === 'files' && historyDiffState) {
5940
+ var file = historyDiffState.files[historyDiffState.fileFocusIndex];
5941
+ if (file) historyShowFile(file.path, file.hunk, true);
5942
+ focusHistoryDiff();
5943
+ } else {
5944
+ var rows = historyVisibleRows();
5945
+ var row = rows.find(function (r) { return r.dataset.sha === historyActiveSha; }) || rows[0];
5946
+ if (row) openHistoryCommit(row.dataset.sha);
5947
+ }
5948
+ return true;
5949
+ }
5950
+ if (historyFocus === 'diff' && handleHistoryDiffKey(e)) { e.preventDefault(); e.stopPropagation(); return true; }
5951
+ return !inSearch && historyFocus !== 'commits';
5952
+ }
5953
+
5508
5954
  (function wireHistory() {
5509
5955
  var list = document.getElementById('history-list');
5510
5956
  if (list) list.addEventListener('click', function (e) {
@@ -5516,8 +5962,18 @@ if (typeof window !== 'undefined') window.__monacoriHistory = { open: openHistor
5516
5962
  var closeBtn = document.getElementById('history-close');
5517
5963
  if (closeBtn) closeBtn.addEventListener('click', closeHistory);
5518
5964
  var view = document.getElementById('history-view');
5965
+ if (view) view.setAttribute('tabindex', '-1');
5519
5966
  if (view) view.addEventListener('keydown', function (e) {
5520
- if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeHistory(); }
5967
+ handleHistoryKey(e);
5968
+ });
5969
+ var detail = document.getElementById('history-detail');
5970
+ if (detail) detail.addEventListener('click', function (e) {
5971
+ var file = e.target.closest && e.target.closest('.history-file[data-file]');
5972
+ if (file && historyDiffState) {
5973
+ e.preventDefault();
5974
+ historyShowFile(file.dataset.file, Number(file.dataset.hunk), true);
5975
+ focusHistoryDiff();
5976
+ }
5521
5977
  });
5522
5978
  })();
5523
5979
  // ===== Go-to-line (Cmd/Ctrl+L), copy caret location (Cmd/Ctrl+K), and the sidebar row action menu. =====