@happy-nut/monacori 0.1.22 → 0.1.23

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.
@@ -1100,6 +1100,11 @@ function handleTreeKey(event) {
1100
1100
  if (event.key === 'ArrowUp') { event.preventDefault(); focusTree(treeFocusIndex - 1); return true; }
1101
1101
  if (event.key === 'PageDown') { event.preventDefault(); focusTree(treeFocusIndex + treePageSize()); return true; }
1102
1102
  if (event.key === 'PageUp') { event.preventDefault(); focusTree(treeFocusIndex - treePageSize()); return true; }
1103
+ if (event.key === 'Enter' && event.altKey) {
1104
+ event.preventDefault();
1105
+ if (row && typeof openTreeRowMenu === 'function') openTreeRowMenu(row); // copy path / Finder / terminal
1106
+ return true;
1107
+ }
1103
1108
  if (event.key === 'Enter') {
1104
1109
  event.preventDefault();
1105
1110
  if (row && row.classList.contains('file-link')) { row.click(); clearTreeFocus(); }
@@ -1137,6 +1142,9 @@ function handleTreeKey(event) {
1137
1142
  function isFloatingModalOpen() {
1138
1143
  var sm = document.getElementById('settings-modal');
1139
1144
  if (sm && !sm.classList.contains('hidden')) return true;
1145
+ var hv = document.getElementById('history-view');
1146
+ if (hv && !hv.classList.contains('hidden')) return true; // history overlay owns the keys (Esc/filter/click)
1147
+ if (document.getElementById('goto-line')) return true; // go-to-line prompt owns the keys until Enter/Esc
1140
1148
  // The merged/memo panels are now docked (inline), not overlays — but while one OWNS focus we still stand
1141
1149
  // down the global nav shortcuts so typing / ▲▼ inside it isn't hijacked. Focus elsewhere -> shortcuts run.
1142
1150
  return isDockFocused();
@@ -1170,6 +1178,12 @@ document.addEventListener('keydown', (event) => {
1170
1178
  openMemoView();
1171
1179
  return;
1172
1180
  }
1181
+ // Cmd/Ctrl+9 toggles the git history view (above the focus guard so a 2nd press closes it from inside).
1182
+ if (!settingsUp && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && (event.code === 'Digit9' || event.key === '9') && typeof toggleHistory === 'function') {
1183
+ event.preventDefault();
1184
+ toggleHistory();
1185
+ return;
1186
+ }
1173
1187
 
1174
1188
  // Settings overlay (or a focused merged/memo dock) captures keys: stand down the rest of the global
1175
1189
  // shortcuts (Cmd+1, F7, Cmd+[/], Cmd+B, …). Each has its own Esc + editing handlers.
@@ -1190,6 +1204,24 @@ document.addEventListener('keydown', (event) => {
1190
1204
  focusOpenFileInTree();
1191
1205
  return;
1192
1206
  }
1207
+ // Cmd/Ctrl+L = go to line (numeric prompt); Cmd/Ctrl+K = copy the caret's file:line. Skip when an
1208
+ // editable field owns focus (a comment composer textarea) so we don't hijack the user's typing.
1209
+ if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && (event.key === 'l' || event.key === 'L')) {
1210
+ var lkae = document.activeElement;
1211
+ if (!(lkae && (lkae.tagName === 'INPUT' || lkae.tagName === 'TEXTAREA' || lkae.tagName === 'SELECT'))) {
1212
+ event.preventDefault();
1213
+ openGotoLine();
1214
+ return;
1215
+ }
1216
+ }
1217
+ if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && (event.key === 'k' || event.key === 'K')) {
1218
+ var kkae = document.activeElement;
1219
+ if (!(kkae && (kkae.tagName === 'INPUT' || kkae.tagName === 'TEXTAREA' || kkae.tagName === 'SELECT'))) {
1220
+ event.preventDefault();
1221
+ copyCaretLocation();
1222
+ return;
1223
+ }
1224
+ }
1193
1225
  if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && event.key === '0') {
1194
1226
  event.preventDefault();
1195
1227
  setTab('changes');
@@ -1483,6 +1515,7 @@ document.querySelector('.activity-rail')?.addEventListener('click', (event) => {
1483
1515
  else if (view === 'files') { setTab('files'); }
1484
1516
  else if (view === 'q' || view === 'c') { toggleMergedRail(view); }
1485
1517
  else if (view === 'memo') { openMemoView(); } // openMemoView already toggles
1518
+ else if (view === 'history') { toggleHistory(); }
1486
1519
  syncRail();
1487
1520
  });
1488
1521
 
@@ -3266,6 +3299,8 @@ function syncRail() {
3266
3299
  setOn('q', !!(merged && merged.dataset.kind === 'q'));
3267
3300
  setOn('c', !!(merged && merged.dataset.kind === 'c'));
3268
3301
  setOn('memo', !!document.getElementById('mc-memo-panel'));
3302
+ var hv = document.getElementById('history-view');
3303
+ setOn('history', !!(hv && !hv.classList.contains('hidden')));
3269
3304
  }
3270
3305
  // Rail click for the merged views toggles: a 2nd click on the open kind closes it (memo already toggles).
3271
3306
  function toggleMergedRail(kind) {
@@ -5175,3 +5210,328 @@ function formatBytes(bytes) {
5175
5210
  if (kib < 1024) return kib.toFixed(1) + ' KiB';
5176
5211
  return (kib / 1024).toFixed(1) + ' MiB';
5177
5212
  }
5213
+ // ===== Git history view (Cmd+9): commit list with graph lanes + per-commit diff. =====
5214
+ // Data comes from the main process (window.monacoriGit.log / .commitDiff); the lane layout is computed
5215
+ // here from each commit's parents. Read-only — the per-commit diff is static diff2html HTML.
5216
+
5217
+ var HISTORY_LANE_W = 14, HISTORY_DOT_R = 3.5, HISTORY_ROW_H = 24;
5218
+ var HISTORY_COLORS = ['#6c9fd4', '#7faf6b', '#d4a857', '#c77dd4', '#d36c6c', '#5bb6b6', '#b0884f', '#8d8df0'];
5219
+ var historyCommits = [];
5220
+ var historyGraph = [];
5221
+ var historyMaxLane = 0;
5222
+ var historyActiveSha = '';
5223
+ var historyLoading = false;
5224
+
5225
+ // Lane layout. Walks commits newest-first, tracking open edges (lanes) by the hash each expects next.
5226
+ // Returns per-row { hash, myLane, color, topEdges, bottomEdges } using LANE INDICES + COLOR INDICES (px-free,
5227
+ // so it's unit-testable). First parent inherits the commit's color so a branch keeps one hue down its line.
5228
+ function computeHistoryGraph(commits) {
5229
+ var lanes = []; // lane index -> hash the lane is waiting to reach (open edge from above)
5230
+ var colorOf = {}; // hash -> color index
5231
+ var next = 0;
5232
+ function colorFor(h) { if (colorOf[h] == null) colorOf[h] = next++; return colorOf[h]; }
5233
+ function freeLane() { for (var i = 0; i < lanes.length; i++) if (lanes[i] == null) return i; lanes.push(null); return lanes.length - 1; }
5234
+ var rows = [];
5235
+ var maxLane = 0;
5236
+ for (var ci = 0; ci < commits.length; ci++) {
5237
+ var c = commits[ci];
5238
+ var incoming = lanes.slice();
5239
+ var myLane = lanes.indexOf(c.hash);
5240
+ if (myLane === -1) myLane = freeLane();
5241
+ var myColor = colorFor(c.hash);
5242
+ lanes[myLane] = c.hash;
5243
+ for (var i = 0; i < lanes.length; i++) if (i !== myLane && lanes[i] === c.hash) lanes[i] = null; // merge other edges in
5244
+ var parents = c.parents || [];
5245
+ var parentLanes = {};
5246
+ if (parents.length === 0) {
5247
+ lanes[myLane] = null; // root commit — the lane ends here
5248
+ } else {
5249
+ lanes[myLane] = parents[0];
5250
+ if (colorOf[parents[0]] == null) colorOf[parents[0]] = myColor; // first parent keeps the hue
5251
+ parentLanes[myLane] = true;
5252
+ for (var p = 1; p < parents.length; p++) {
5253
+ var ex = lanes.indexOf(parents[p]);
5254
+ var l = ex !== -1 ? ex : freeLane();
5255
+ lanes[l] = parents[p];
5256
+ colorFor(parents[p]);
5257
+ parentLanes[l] = true;
5258
+ }
5259
+ }
5260
+ var outgoing = lanes.slice();
5261
+ var topEdges = [];
5262
+ for (var a = 0; a < incoming.length; a++) {
5263
+ if (incoming[a] == null) continue;
5264
+ topEdges.push({ from: a, to: incoming[a] === c.hash ? myLane : a, color: colorOf[incoming[a]] });
5265
+ }
5266
+ var bottomEdges = [];
5267
+ for (var b = 0; b < outgoing.length; b++) {
5268
+ if (outgoing[b] == null) continue;
5269
+ bottomEdges.push({ from: parentLanes[b] ? myLane : b, to: b, color: colorOf[outgoing[b]] });
5270
+ }
5271
+ for (var m = 0; m < Math.max(incoming.length, outgoing.length); m++) {
5272
+ if (incoming[m] != null || outgoing[m] != null) maxLane = Math.max(maxLane, m);
5273
+ }
5274
+ maxLane = Math.max(maxLane, myLane);
5275
+ rows.push({ hash: c.hash, myLane: myLane, color: myColor, topEdges: topEdges, bottomEdges: bottomEdges });
5276
+ }
5277
+ rows.maxLane = maxLane;
5278
+ return rows;
5279
+ }
5280
+ if (typeof window !== 'undefined') window.computeHistoryGraph = computeHistoryGraph; // exposed for tests
5281
+
5282
+ function historyLaneX(l) { return 9 + l * HISTORY_LANE_W; }
5283
+ function historyColor(i) { return HISTORY_COLORS[i % HISTORY_COLORS.length]; }
5284
+ function historyRowSvg(row) {
5285
+ var w = historyLaneX(historyMaxLane) + 9, h = HISTORY_ROW_H, mid = h / 2;
5286
+ var s = '<svg class="hgraph" width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '" aria-hidden="true">';
5287
+ var edge = function (e, y1, y2) {
5288
+ var x1 = historyLaneX(e.from), x2 = historyLaneX(e.to);
5289
+ var c1 = (y1 + y2) / 2;
5290
+ return '<path d="M' + x1 + ' ' + y1 + ' C ' + x1 + ' ' + c1 + ', ' + x2 + ' ' + c1 + ', ' + x2 + ' ' + y2 + '" stroke="' + historyColor(e.color) + '" fill="none" stroke-width="1.6"/>';
5291
+ };
5292
+ row.topEdges.forEach(function (e) { s += edge(e, 0, mid); });
5293
+ row.bottomEdges.forEach(function (e) { s += edge(e, mid, h); });
5294
+ s += '<circle cx="' + historyLaneX(row.myLane) + '" cy="' + mid + '" r="' + HISTORY_DOT_R + '" fill="' + historyColor(row.color) + '"/></svg>';
5295
+ return s;
5296
+ }
5297
+
5298
+ // "HEAD -> main, origin/main, tag: v1" -> small badges (HEAD/branch/tag styled distinctly).
5299
+ function historyRefBadges(refs) {
5300
+ if (!refs || !refs.trim()) return '';
5301
+ return refs.split(',').map(function (r) {
5302
+ r = r.trim();
5303
+ if (!r) return '';
5304
+ var cls = 'href-branch', label = r;
5305
+ if (r.indexOf('tag:') === 0) { cls = 'href-tag'; label = r.replace('tag:', '').trim(); }
5306
+ else if (r.indexOf('HEAD') === 0) { cls = 'href-head'; }
5307
+ else if (r.indexOf('origin/') === 0 || r.indexOf('/') !== -1) { cls = 'href-remote'; }
5308
+ return '<span class="href ' + cls + '">' + escapeHtml(label) + '</span>';
5309
+ }).join('');
5310
+ }
5311
+
5312
+ function historyShortDate(iso) {
5313
+ if (!iso) return '';
5314
+ // 2026-06-20T21:03:11+09:00 -> "2026-06-20 21:03"
5315
+ var m = String(iso).match(/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/);
5316
+ return m ? m[1] + ' ' + m[2] : String(iso).slice(0, 16);
5317
+ }
5318
+
5319
+ function renderHistoryList() {
5320
+ var list = document.getElementById('history-list');
5321
+ if (!list) return;
5322
+ if (!historyCommits.length) {
5323
+ list.innerHTML = '<div class="quick-open-empty">' + escapeHtml(t(historyLoading ? 'history.loading' : 'history.empty')) + '</div>';
5324
+ return;
5325
+ }
5326
+ list.style.setProperty('--hgraph-w', (historyLaneX(historyMaxLane) + 9) + 'px');
5327
+ list.innerHTML = historyCommits.map(function (c, i) {
5328
+ return '<button type="button" class="hrow' + (c.hash === historyActiveSha ? ' active' : '') + '" data-sha="' + escapeHtml(c.hash) + '">'
5329
+ + '<span class="hgraph-cell">' + historyRowSvg(historyGraph[i]) + '</span>'
5330
+ + '<span class="hmsg">' + historyRefBadges(c.refs) + escapeHtml(c.subject) + '</span>'
5331
+ + '<span class="hauthor">' + escapeHtml(c.author) + '</span>'
5332
+ + '<span class="hdate">' + escapeHtml(historyShortDate(c.date)) + '</span>'
5333
+ + '</button>';
5334
+ }).join('');
5335
+ }
5336
+
5337
+ // Text filter (subject / author). The graph only reads right on the full contiguous history, so filtering
5338
+ // hides the graph column (IntelliJ does the same) and just shows matching rows.
5339
+ function applyHistoryFilter() {
5340
+ var input = document.getElementById('history-search');
5341
+ var list = document.getElementById('history-list');
5342
+ if (!list) return;
5343
+ var q = (input && input.value || '').trim().toLowerCase();
5344
+ list.classList.toggle('filtering', q.length > 0);
5345
+ var rows = list.querySelectorAll('.hrow');
5346
+ for (var i = 0; i < rows.length; i++) {
5347
+ var c = historyCommits[i];
5348
+ var hit = !q || (c.subject + '\n' + c.author + '\n' + c.hash).toLowerCase().indexOf(q) !== -1;
5349
+ rows[i].classList.toggle('hidden', !hit);
5350
+ }
5351
+ }
5352
+
5353
+ function openHistoryCommit(sha) {
5354
+ if (!sha || !window.monacoriGit) return;
5355
+ historyActiveSha = sha;
5356
+ var list = document.getElementById('history-list');
5357
+ if (list) list.querySelectorAll('.hrow').forEach(function (r) { r.classList.toggle('active', r.dataset.sha === sha); });
5358
+ var detail = document.getElementById('history-detail');
5359
+ if (detail) detail.innerHTML = '<div class="quick-open-empty">' + escapeHtml(t('history.loading')) + '</div>';
5360
+ Promise.resolve(window.monacoriGit.commitDiff(sha)).then(function (d) {
5361
+ if (!d || historyActiveSha !== sha) return; // selection moved on while loading
5362
+ renderHistoryDetail(d);
5363
+ }, function () {});
5364
+ }
5365
+
5366
+ function renderHistoryDetail(d) {
5367
+ var detail = document.getElementById('history-detail');
5368
+ if (!detail) return;
5369
+ var head = '<div class="history-detail-head">'
5370
+ + '<div class="hd-msg">' + escapeHtml(d.message || '').replace(/\n/g, '<br>') + '</div>'
5371
+ + '<div class="hd-meta"><span class="hd-hash">' + escapeHtml((d.hash || '').slice(0, 10)) + '</span>'
5372
+ + '<span class="hd-author">' + escapeHtml(d.author) + (d.email ? ' &lt;' + escapeHtml(d.email) + '&gt;' : '') + '</span>'
5373
+ + '<span class="hd-date">' + escapeHtml(historyShortDate(d.date)) + '</span>'
5374
+ + historyRefBadges(d.refs) + '</div></div>';
5375
+ var body = (d.diffHtml && d.diffHtml.trim())
5376
+ ? '<div class="history-diff diff2html-container">' + d.diffHtml + '</div>'
5377
+ : '<div class="quick-open-empty">' + escapeHtml(t(d.isMerge ? 'history.merge' : 'history.noDiff')) + '</div>';
5378
+ detail.innerHTML = head + body;
5379
+ }
5380
+
5381
+ function isHistoryOpen() {
5382
+ var v = document.getElementById('history-view');
5383
+ return !!(v && !v.classList.contains('hidden'));
5384
+ }
5385
+ function closeHistory() {
5386
+ var v = document.getElementById('history-view');
5387
+ if (v) v.classList.add('hidden');
5388
+ if (typeof syncRail === 'function') syncRail();
5389
+ }
5390
+ function openHistory() {
5391
+ var v = document.getElementById('history-view');
5392
+ if (!v) return;
5393
+ if (!window.monacoriGit) return; // browser/serve mode: no git bridge
5394
+ v.classList.remove('hidden');
5395
+ if (typeof syncRail === 'function') syncRail();
5396
+ var search = document.getElementById('history-search');
5397
+ if (search) { search.value = ''; }
5398
+ applyHistoryFilter();
5399
+ historyLoading = true;
5400
+ renderHistoryList();
5401
+ Promise.resolve(window.monacoriGit.log({ limit: 300 })).then(function (commits) {
5402
+ historyLoading = false;
5403
+ historyCommits = Array.isArray(commits) ? commits : [];
5404
+ historyGraph = computeHistoryGraph(historyCommits);
5405
+ historyMaxLane = historyGraph.maxLane || 0;
5406
+ renderHistoryList();
5407
+ var detail = document.getElementById('history-detail');
5408
+ if (detail) detail.innerHTML = '<div class="quick-open-empty">' + escapeHtml(t('history.selectCommit')) + '</div>';
5409
+ if (historyCommits[0]) openHistoryCommit(historyCommits[0].hash); // preview the newest commit
5410
+ if (search) setTimeout(function () { try { search.focus(); } catch (e) {} }, 0);
5411
+ }, function () { historyLoading = false; renderHistoryList(); });
5412
+ }
5413
+ function toggleHistory() { if (isHistoryOpen()) closeHistory(); else openHistory(); }
5414
+ if (typeof window !== 'undefined') window.__monacoriHistory = { open: openHistory, close: closeHistory, toggle: toggleHistory, isOpen: isHistoryOpen };
5415
+
5416
+ (function wireHistory() {
5417
+ var list = document.getElementById('history-list');
5418
+ if (list) list.addEventListener('click', function (e) {
5419
+ var row = e.target.closest && e.target.closest('.hrow[data-sha]');
5420
+ if (row) openHistoryCommit(row.dataset.sha);
5421
+ });
5422
+ var search = document.getElementById('history-search');
5423
+ if (search) search.addEventListener('input', applyHistoryFilter);
5424
+ var closeBtn = document.getElementById('history-close');
5425
+ if (closeBtn) closeBtn.addEventListener('click', closeHistory);
5426
+ var view = document.getElementById('history-view');
5427
+ if (view) view.addEventListener('keydown', function (e) {
5428
+ if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeHistory(); }
5429
+ });
5430
+ })();
5431
+ // ===== Go-to-line (Cmd/Ctrl+L), copy caret location (Cmd/Ctrl+K), and the sidebar row action menu. =====
5432
+
5433
+ // Programmatic clipboard write. Electron's bridge is reliable on file://; navigator.clipboard is the fallback.
5434
+ function copyTextToClipboard(text) {
5435
+ try { if (window.monacoriClipboard && typeof window.monacoriClipboard.write === 'function') { window.monacoriClipboard.write(text); return true; } } catch (e) {}
5436
+ try { if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text); return true; } } catch (e) {}
5437
+ return false;
5438
+ }
5439
+
5440
+ // "path:line" for the current caret — source view (the painted file) or the diff caret. '' if neither.
5441
+ function caretLocation() {
5442
+ if (typeof isSourceViewerVisible === 'function' && isSourceViewerVisible()) {
5443
+ var sv = document.getElementById('source-viewer');
5444
+ var p = (sv && sv.dataset.openPath) || '';
5445
+ if (p && typeof viewerCursor !== 'undefined' && viewerCursor && viewerCursor.path === p) return p + ':' + (viewerCursor.lineIndex + 1);
5446
+ if (p) return p;
5447
+ }
5448
+ if (typeof isDiffViewVisible === 'function' && isDiffViewVisible() && typeof diffCursor !== 'undefined' && diffCursor) {
5449
+ var wrap = diffWrapperByPath(diffCursor.path);
5450
+ var row = wrap ? diffRowAt(wrap, diffCursor.side, diffCursor.rowIndex) : null;
5451
+ var ln = row ? diffLineNumber(row) : null;
5452
+ return diffCursor.path + (ln ? ':' + ln : '');
5453
+ }
5454
+ return '';
5455
+ }
5456
+
5457
+ // Cmd/Ctrl+K — copy the caret's file:line to the clipboard.
5458
+ function copyCaretLocation() {
5459
+ var loc = caretLocation();
5460
+ if (!loc) return;
5461
+ if (copyTextToClipboard(loc) && typeof showToast === 'function') showToast(t('goto.copied') + ' ' + loc);
5462
+ }
5463
+
5464
+ // Diff view: place the caret on the row whose (new, then old) line number matches n, in the active file.
5465
+ function gotoDiffLine(n) {
5466
+ var path = (typeof diffCursor !== 'undefined' && diffCursor && diffCursor.path) || '';
5467
+ if (!path && typeof diffActiveWrapper === 'function') {
5468
+ var w = diffActiveWrapper();
5469
+ var nm = w && w.querySelector('.d2h-file-name');
5470
+ if (nm && nm.textContent) path = nm.textContent.trim();
5471
+ }
5472
+ var wrap = path && diffWrapperByPath(path);
5473
+ if (!wrap) return;
5474
+ var sides = [(diffCursor && diffCursor.side) || 'new', 'new', 'old'];
5475
+ for (var s = 0; s < sides.length; s++) {
5476
+ var rows = diffRowsOf(diffSideTable(wrap, sides[s]));
5477
+ for (var i = 0; i < rows.length; i++) {
5478
+ if (diffLineNumber(rows[i]) === n) { setDiffCursor(path, sides[s], i, 0, true); return; }
5479
+ }
5480
+ }
5481
+ }
5482
+
5483
+ function gotoLineJump(n) {
5484
+ if (!(n >= 1)) return;
5485
+ if (typeof isSourceViewerVisible === 'function' && isSourceViewerVisible()) {
5486
+ var sv = document.getElementById('source-viewer');
5487
+ var p = (sv && sv.dataset.openPath) || '';
5488
+ var f = p && sourceByPath.get(p);
5489
+ if (f && f.embedded && typeof f.content === 'string') {
5490
+ var max = f.content.split(/\r?\n/).length;
5491
+ setSourceCursor(p, Math.max(0, Math.min(max - 1, n - 1)), 0, true, -1);
5492
+ return;
5493
+ }
5494
+ }
5495
+ if (typeof isDiffViewVisible === 'function' && isDiffViewVisible()) gotoDiffLine(n);
5496
+ }
5497
+
5498
+ // Cmd/Ctrl+L — a small numeric prompt; Enter jumps, Esc closes.
5499
+ function openGotoLine() {
5500
+ if (!((typeof isSourceViewerVisible === 'function' && isSourceViewerVisible()) || (typeof isDiffViewVisible === 'function' && isDiffViewVisible()))) return;
5501
+ var prior = document.getElementById('goto-line');
5502
+ if (prior) prior.remove();
5503
+ var box = document.createElement('div');
5504
+ box.id = 'goto-line';
5505
+ box.className = 'goto-line';
5506
+ var input = document.createElement('input');
5507
+ input.type = 'text';
5508
+ input.inputMode = 'numeric';
5509
+ input.className = 'goto-line-input';
5510
+ input.placeholder = t('goto.placeholder');
5511
+ box.appendChild(input);
5512
+ document.body.appendChild(box);
5513
+ function close() { box.remove(); document.removeEventListener('keydown', onKey, true); }
5514
+ function onKey(e) {
5515
+ if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); }
5516
+ else if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); var n = parseInt(input.value, 10); close(); if (n >= 1) gotoLineJump(n); }
5517
+ }
5518
+ // Capture phase so Enter/Esc are handled here before the global keymap (which is on bubble).
5519
+ document.addEventListener('keydown', onKey, true);
5520
+ setTimeout(function () { try { input.focus(); } catch (e) {} }, 0);
5521
+ }
5522
+
5523
+ // Sidebar Opt+Enter: actions for a focused file row (copy path / reveal in Finder / open terminal here).
5524
+ function openTreeRowMenu(row) {
5525
+ if (!row) return;
5526
+ var path = row.dataset.sourceFile || row.dataset.file || '';
5527
+ if (!path) return;
5528
+ var r = row.getBoundingClientRect();
5529
+ var items = [
5530
+ { label: t('menu.copyPath'), onSelect: function () { if (copyTextToClipboard(path) && typeof showToast === 'function') showToast(t('goto.copied') + ' ' + path); } },
5531
+ ];
5532
+ if (window.monacoriApp && typeof window.monacoriApp.revealInFinder === 'function') {
5533
+ items.push({ label: t('menu.revealFinder'), onSelect: function () { try { window.monacoriApp.revealInFinder(path); } catch (e) {} } });
5534
+ items.push({ label: t('menu.openTerminal'), onSelect: function () { try { window.monacoriApp.openTerminalAt(path); } catch (e) {} } });
5535
+ }
5536
+ showCustomDropdown(Math.round(r.left + 14), Math.round(r.bottom + 2), items, Math.round(r.top));
5537
+ }