mbeditor 0.5.6 → 0.6.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +53 -28
- data/app/assets/javascripts/mbeditor/components/FileTree.js +168 -116
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +361 -84
- data/app/assets/javascripts/mbeditor/editor_plugins.js +32 -4
- data/app/assets/javascripts/mbeditor/file_service.js +7 -2
- data/app/assets/javascripts/mbeditor/git_service.js +2 -1
- data/app/assets/javascripts/mbeditor/tab_manager.js +1 -1
- data/app/assets/stylesheets/mbeditor/editor.css +97 -65
- data/app/controllers/mbeditor/editors_controller.rb +21 -8
- data/app/services/mbeditor/rails_related_files_service.rb +234 -0
- data/config/routes.rb +1 -0
- data/lib/mbeditor/configuration.rb +2 -1
- data/lib/mbeditor/version.rb +1 -1
- metadata +3 -2
|
@@ -66,6 +66,33 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
66
66
|
showDotFiles: false
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
function spacesToTabs(code, indentSize) {
|
|
70
|
+
var unit = ' '.repeat(indentSize || 2);
|
|
71
|
+
return code.split('\n').map(function(line) {
|
|
72
|
+
var tabs = '';
|
|
73
|
+
while (line.startsWith(unit)) { tabs += '\t'; line = line.slice(unit.length); }
|
|
74
|
+
return tabs + line;
|
|
75
|
+
}).join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function diffLines(oldLines, newLines) {
|
|
79
|
+
var n = oldLines.length, m = newLines.length;
|
|
80
|
+
var dp = [];
|
|
81
|
+
for (var i = 0; i <= n; i++) { dp.push(new Array(m + 1).fill(0)); }
|
|
82
|
+
for (var i = 1; i <= n; i++) {
|
|
83
|
+
for (var j = 1; j <= m; j++) {
|
|
84
|
+
dp[i][j] = oldLines[i-1] === newLines[j-1] ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
var changed = [], i = n, j = m;
|
|
88
|
+
while (i > 0 || j > 0) {
|
|
89
|
+
if (i > 0 && j > 0 && oldLines[i-1] === newLines[j-1]) { i--; j--; }
|
|
90
|
+
else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) { changed.push(j); j--; }
|
|
91
|
+
else { i--; }
|
|
92
|
+
}
|
|
93
|
+
return changed;
|
|
94
|
+
}
|
|
95
|
+
|
|
69
96
|
var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
70
97
|
var title = _ref.title;
|
|
71
98
|
var iconClass = _ref.iconClass;
|
|
@@ -284,6 +311,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
284
311
|
var sidebarCollapsed = _useStateSC2[0];
|
|
285
312
|
var setSidebarCollapsed = _useStateSC2[1];
|
|
286
313
|
|
|
314
|
+
var _useStateRFMap = useState({});
|
|
315
|
+
var _useStateRFMap2 = _slicedToArray(_useStateRFMap, 2);
|
|
316
|
+
var railsFilesMap = _useStateRFMap2[0];
|
|
317
|
+
var setRailsFilesMap = _useStateRFMap2[1];
|
|
318
|
+
|
|
319
|
+
var _useStateRFC = useState({});
|
|
320
|
+
var _useStateRFC2 = _slicedToArray(_useStateRFC, 2);
|
|
321
|
+
var railsGroupsCollapsed = _useStateRFC2[0];
|
|
322
|
+
var setRailsGroupsCollapsed = _useStateRFC2[1];
|
|
323
|
+
|
|
287
324
|
var _useState9 = useState({});
|
|
288
325
|
|
|
289
326
|
var _useState92 = _slicedToArray(_useState9, 2);
|
|
@@ -496,6 +533,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
496
533
|
var prevGitBranchRef = useRef(null);
|
|
497
534
|
var isSwitchingBranchRef = useRef(false);
|
|
498
535
|
var stateRestoredRef = useRef(false);
|
|
536
|
+
var ctrlWPendingRef = useRef(false);
|
|
537
|
+
var ctrlWTimeoutRef = useRef(null);
|
|
499
538
|
|
|
500
539
|
// ── Draft backup helpers ─────────────────────────────────────────────────
|
|
501
540
|
var draftWriteTimerRef = useRef({});
|
|
@@ -506,7 +545,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
506
545
|
return 'mbeditor_draft\x00' + base + '\x00' + path;
|
|
507
546
|
};
|
|
508
547
|
var _saveDraftNow = function _saveDraftNow(path, content) {
|
|
509
|
-
|
|
548
|
+
var doWrite = function() {
|
|
549
|
+
try { localStorage.setItem(_draftKey(path), JSON.stringify({ content: content, ts: Date.now() })); } catch (e) {}
|
|
550
|
+
};
|
|
551
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
552
|
+
requestIdleCallback(doWrite, { timeout: 2000 });
|
|
553
|
+
} else {
|
|
554
|
+
doWrite();
|
|
555
|
+
}
|
|
510
556
|
};
|
|
511
557
|
var _clearDraft = function _clearDraft(path) {
|
|
512
558
|
try { localStorage.removeItem(_draftKey(path)); } catch (e) {}
|
|
@@ -1000,7 +1046,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1000
1046
|
var rect = body.getBoundingClientRect();
|
|
1001
1047
|
var reservedRight = EDITOR_MIN_WIDTH + (showGitPanelRef.current ? gitPanelWidthRef.current : 0);
|
|
1002
1048
|
var maxSidebarWidth = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, rect.width - reservedRight));
|
|
1003
|
-
var nextWidth = clientX - rect.left;
|
|
1049
|
+
var nextWidth = clientX - rect.left - SIDEBAR_COLLAPSED_WIDTH;
|
|
1004
1050
|
setSidebarWidth(clamp(nextWidth, SIDEBAR_MIN_WIDTH, maxSidebarWidth));
|
|
1005
1051
|
}
|
|
1006
1052
|
|
|
@@ -1044,8 +1090,46 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1044
1090
|
}
|
|
1045
1091
|
};
|
|
1046
1092
|
|
|
1093
|
+
// Capture-phase listener for vim Ctrl+W window navigation.
|
|
1094
|
+
// Runs before Monaco (and the browser) so neither can swallow the keystrokes.
|
|
1095
|
+
// Phase 1 — Ctrl+W: prevent tab-close and arm the pending flag.
|
|
1096
|
+
// Phase 2 — next key: act on it here (still capture phase) so Monaco-vim
|
|
1097
|
+
// cannot consume it as a word-motion or other binding.
|
|
1098
|
+
var onCtrlWCapture = function(e) {
|
|
1099
|
+
// Phase 2: a previous Ctrl+W is pending — consume the follow-up key.
|
|
1100
|
+
if (ctrlWPendingRef.current) {
|
|
1101
|
+
ctrlWPendingRef.current = false;
|
|
1102
|
+
if (ctrlWTimeoutRef.current) { clearTimeout(ctrlWTimeoutRef.current); ctrlWTimeoutRef.current = null; }
|
|
1103
|
+
var _st = EditorStore.getState();
|
|
1104
|
+
var _cur = _st.focusedPaneId;
|
|
1105
|
+
var _target;
|
|
1106
|
+
if (e.key === '1') _target = 1;
|
|
1107
|
+
else if (e.key === '2') _target = 2;
|
|
1108
|
+
else if (e.key === 'h') _target = 1;
|
|
1109
|
+
else _target = _cur === 1 ? 2 : 1; // w, l, Ctrl+W, or anything else → cycle
|
|
1110
|
+
if (_target !== _cur) {
|
|
1111
|
+
if (typeof TabManager !== 'undefined') TabManager.focusPane(_target);
|
|
1112
|
+
window.dispatchEvent(new CustomEvent('mbeditor:focusPane', { detail: { paneId: _target } }));
|
|
1113
|
+
}
|
|
1114
|
+
e.preventDefault();
|
|
1115
|
+
e.stopPropagation();
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
// Phase 1: intercept Ctrl+W itself when vim mode is on.
|
|
1119
|
+
if (e.metaKey || e.shiftKey || e.altKey) return;
|
|
1120
|
+
if (!e.ctrlKey || (e.key !== 'w' && e.key !== 'W')) return;
|
|
1121
|
+
var prefs = EditorStore.getState().editorPrefs;
|
|
1122
|
+
if (!prefs || !prefs.vimMode) return;
|
|
1123
|
+
e.preventDefault();
|
|
1124
|
+
e.stopPropagation();
|
|
1125
|
+
if (ctrlWTimeoutRef.current) clearTimeout(ctrlWTimeoutRef.current);
|
|
1126
|
+
ctrlWPendingRef.current = true;
|
|
1127
|
+
ctrlWTimeoutRef.current = setTimeout(function() { ctrlWPendingRef.current = false; }, 1500);
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1047
1130
|
window.addEventListener('keydown', onKeyDown);
|
|
1048
1131
|
document.addEventListener('keydown', onZenCapture, true);
|
|
1132
|
+
document.addEventListener('keydown', onCtrlWCapture, true);
|
|
1049
1133
|
window.addEventListener('mousemove', handleMouseMove);
|
|
1050
1134
|
window.addEventListener('mouseup', handleMouseUp);
|
|
1051
1135
|
return function () {
|
|
@@ -1056,8 +1140,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1056
1140
|
cancelAnimationFrame(resizeRafRef.current);
|
|
1057
1141
|
resizeRafRef.current = null;
|
|
1058
1142
|
}
|
|
1143
|
+
if (ctrlWTimeoutRef.current) { clearTimeout(ctrlWTimeoutRef.current); ctrlWTimeoutRef.current = null; }
|
|
1059
1144
|
window.removeEventListener('keydown', onKeyDown);
|
|
1060
1145
|
document.removeEventListener('keydown', onZenCapture, true);
|
|
1146
|
+
document.removeEventListener('keydown', onCtrlWCapture, true);
|
|
1061
1147
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
1062
1148
|
window.removeEventListener('mouseup', handleMouseUp);
|
|
1063
1149
|
document.body.style.cursor = '';
|
|
@@ -1133,8 +1219,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1133
1219
|
FileService.getTree().then(function (data) {
|
|
1134
1220
|
var newData = data || [];
|
|
1135
1221
|
setTreeData(function (prevData) {
|
|
1136
|
-
|
|
1137
|
-
SearchService.buildIndex(newData);
|
|
1222
|
+
var sig = function(d) { return d.length + ':' + d.map(function(n) { return n.name; }).join(','); };
|
|
1223
|
+
if (sig(newData) !== sig(prevData)) SearchService.buildIndex(newData);
|
|
1138
1224
|
return newData;
|
|
1139
1225
|
});
|
|
1140
1226
|
checkOpenTabsForExternalChanges();
|
|
@@ -1221,8 +1307,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1221
1307
|
FileService.getTree().then(function (data) {
|
|
1222
1308
|
var newData = data || [];
|
|
1223
1309
|
setTreeData(function (prevData) {
|
|
1224
|
-
|
|
1225
|
-
SearchService.buildIndex(newData);
|
|
1310
|
+
var sig = function(d) { return d.length + ':' + d.map(function(n) { return n.name; }).join(','); };
|
|
1311
|
+
if (sig(newData) !== sig(prevData)) SearchService.buildIndex(newData);
|
|
1226
1312
|
return newData;
|
|
1227
1313
|
});
|
|
1228
1314
|
}).catch(function () {}); // silently ignore auto-refresh errors
|
|
@@ -1435,6 +1521,108 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1435
1521
|
return function() { window.removeEventListener('beforeinstallprompt', handler); };
|
|
1436
1522
|
}, []);
|
|
1437
1523
|
|
|
1524
|
+
var resourceLabelFromPath = function(p) {
|
|
1525
|
+
if (!p) return null;
|
|
1526
|
+
var parts = p.split('/');
|
|
1527
|
+
var file = parts[parts.length - 1];
|
|
1528
|
+
var name;
|
|
1529
|
+
if (parts[0] === 'app') {
|
|
1530
|
+
if (parts[1] === 'controllers') name = file.replace(/_controller\.rb$/, '');
|
|
1531
|
+
else if (parts[1] === 'models') name = file.replace(/\.rb$/, '');
|
|
1532
|
+
else if (parts[1] === 'views' && parts.length >= 4) name = parts[2];
|
|
1533
|
+
else if (parts[1] === 'helpers') name = file.replace(/_helper\.rb$/, '');
|
|
1534
|
+
else return null;
|
|
1535
|
+
} else if (parts[0] === 'test' || parts[0] === 'spec') {
|
|
1536
|
+
if (parts[1] === 'controllers') name = file.replace(/_controller_(test|spec)\.rb$/, '');
|
|
1537
|
+
else if (parts[1] === 'models') name = file.replace(/_(test|spec)\.rb$/, '');
|
|
1538
|
+
else return null;
|
|
1539
|
+
} else { return null; }
|
|
1540
|
+
var seg = (name || '').split('/').pop() || name || '';
|
|
1541
|
+
// Normalize plural→singular so views/users and models/user share one group
|
|
1542
|
+
seg = seg.replace(/ies$/, 'y')
|
|
1543
|
+
.replace(/([^aeiou])es$/, '$1')
|
|
1544
|
+
.replace(/([^s])s$/, '$1');
|
|
1545
|
+
return seg.replace(/_/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
var RAILS_MAX_RESOURCES = 10;
|
|
1549
|
+
|
|
1550
|
+
// Map resource label → representative path (capped at RAILS_MAX_RESOURCES, focused pane first)
|
|
1551
|
+
var railsResourceDeps = (function() {
|
|
1552
|
+
var deps = {};
|
|
1553
|
+
var panesOrdered = state.panes.slice().sort(function(a, b) {
|
|
1554
|
+
return a.id === state.focusedPaneId ? -1 : b.id === state.focusedPaneId ? 1 : 0;
|
|
1555
|
+
});
|
|
1556
|
+
panesOrdered.forEach(function(p) {
|
|
1557
|
+
var tabs = p.tabs.slice().sort(function(a, b) {
|
|
1558
|
+
return a.id === p.activeTabId ? -1 : b.id === p.activeTabId ? 1 : 0;
|
|
1559
|
+
});
|
|
1560
|
+
tabs.forEach(function(t) {
|
|
1561
|
+
if (Object.keys(deps).length >= RAILS_MAX_RESOURCES) return;
|
|
1562
|
+
if (!t.path || t.path === '__settings__' || t.path.startsWith('mbeditor://')) return;
|
|
1563
|
+
var label = resourceLabelFromPath(t.path);
|
|
1564
|
+
if (label && !deps[label]) deps[label] = t.path;
|
|
1565
|
+
});
|
|
1566
|
+
});
|
|
1567
|
+
return deps;
|
|
1568
|
+
})();
|
|
1569
|
+
var railsResourceDepStr = Object.keys(railsResourceDeps).sort().join('|');
|
|
1570
|
+
|
|
1571
|
+
var railsOverflow = (function() {
|
|
1572
|
+
var all = {};
|
|
1573
|
+
state.panes.forEach(function(p) {
|
|
1574
|
+
p.tabs.forEach(function(t) {
|
|
1575
|
+
if (!t.path || t.path === '__settings__' || t.path.startsWith('mbeditor://')) return;
|
|
1576
|
+
var label = resourceLabelFromPath(t.path);
|
|
1577
|
+
if (label) all[label] = true;
|
|
1578
|
+
});
|
|
1579
|
+
});
|
|
1580
|
+
return Math.max(0, Object.keys(all).length - Object.keys(railsResourceDeps).length);
|
|
1581
|
+
})();
|
|
1582
|
+
|
|
1583
|
+
var dirtyPaths = (function() {
|
|
1584
|
+
var set = {};
|
|
1585
|
+
state.panes.forEach(function(p) {
|
|
1586
|
+
p.tabs.forEach(function(t) {
|
|
1587
|
+
if (t.dirty && t.path) set[t.path] = true;
|
|
1588
|
+
});
|
|
1589
|
+
});
|
|
1590
|
+
return set;
|
|
1591
|
+
})();
|
|
1592
|
+
|
|
1593
|
+
useEffect(function() {
|
|
1594
|
+
if (activeSidebarTab !== 'rails') return;
|
|
1595
|
+
var labels = Object.keys(railsResourceDeps);
|
|
1596
|
+
if (labels.length === 0) { setRailsFilesMap({}); return; }
|
|
1597
|
+
setRailsFilesMap(function(prev) {
|
|
1598
|
+
var next = {};
|
|
1599
|
+
labels.forEach(function(label) {
|
|
1600
|
+
next[label] = prev[label] ? { files: prev[label].files, loading: true } : { files: null, loading: true };
|
|
1601
|
+
});
|
|
1602
|
+
return next;
|
|
1603
|
+
});
|
|
1604
|
+
labels.forEach(function(label) {
|
|
1605
|
+
var path = railsResourceDeps[label];
|
|
1606
|
+
FileService.getRelatedFiles(path).then(function(data) {
|
|
1607
|
+
setRailsFilesMap(function(prev) {
|
|
1608
|
+
if (!prev.hasOwnProperty(label)) return prev;
|
|
1609
|
+
var next = Object.assign({}, prev);
|
|
1610
|
+
var update = {};
|
|
1611
|
+
update[label] = { files: data, loading: false };
|
|
1612
|
+
return Object.assign(next, update);
|
|
1613
|
+
});
|
|
1614
|
+
})['catch'](function() {
|
|
1615
|
+
setRailsFilesMap(function(prev) {
|
|
1616
|
+
if (!prev.hasOwnProperty(label)) return prev;
|
|
1617
|
+
var next = Object.assign({}, prev);
|
|
1618
|
+
var update = {};
|
|
1619
|
+
update[label] = { files: null, loading: false };
|
|
1620
|
+
return Object.assign(next, update);
|
|
1621
|
+
});
|
|
1622
|
+
});
|
|
1623
|
+
});
|
|
1624
|
+
}, [activeSidebarTab, railsResourceDepStr]);
|
|
1625
|
+
|
|
1438
1626
|
var focusedPane = state.panes.find(function (p) {
|
|
1439
1627
|
return p.id === state.focusedPaneId;
|
|
1440
1628
|
}) || state.panes[0] || null;
|
|
@@ -1778,7 +1966,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1778
1966
|
return _extends({}, prev, { format: true });
|
|
1779
1967
|
});
|
|
1780
1968
|
EditorStore.setStatus("Formatting...", "info");
|
|
1781
|
-
|
|
1969
|
+
var useTabs = editorPrefs.insertSpaces === false;
|
|
1970
|
+
var originalContent = activeTab.content;
|
|
1971
|
+
var codeToFormat = useTabs ? spacesToTabs(originalContent, 2) : originalContent;
|
|
1972
|
+
FileService.formatFile(activeTab.path, codeToFormat).then(function (res) {
|
|
1782
1973
|
if (res.content) {
|
|
1783
1974
|
// Update content and mark dirty — user decides when to save.
|
|
1784
1975
|
// The executeEdits path in EditorPanel preserves the undo stack.
|
|
@@ -1789,6 +1980,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1789
1980
|
return p;
|
|
1790
1981
|
});
|
|
1791
1982
|
EditorStore.setState({ panes: newPanes });
|
|
1983
|
+
|
|
1984
|
+
// Highlight changed lines briefly
|
|
1985
|
+
var monacoEditor = window.__mbeditorActiveEditor;
|
|
1986
|
+
if (monacoEditor && res.content !== originalContent) {
|
|
1987
|
+
var changedLineNums = diffLines(originalContent.split('\n'), res.content.split('\n'));
|
|
1988
|
+
if (changedLineNums.length > 0) {
|
|
1989
|
+
var decorations = changedLineNums.map(function(ln) {
|
|
1990
|
+
return { range: new monaco.Range(ln, 1, ln, 1), options: { isWholeLine: true, className: 'mbeditor-format-changed' } };
|
|
1991
|
+
});
|
|
1992
|
+
var ids = monacoEditor.deltaDecorations([], decorations);
|
|
1993
|
+
setTimeout(function() { monacoEditor.deltaDecorations(ids, []); }, 3000);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1792
1996
|
}
|
|
1793
1997
|
EditorStore.setStatus("Formatted (Unsaved)", "success");
|
|
1794
1998
|
GitService.fetchStatus();
|
|
@@ -2266,13 +2470,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2266
2470
|
document.body.style.userSelect = 'none';
|
|
2267
2471
|
};
|
|
2268
2472
|
|
|
2269
|
-
var
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2473
|
+
var handleActivityBarClick = function handleActivityBarClick(tab) {
|
|
2474
|
+
if (tab === 'settings') {
|
|
2475
|
+
openSettingsTab();
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
if (!sidebarCollapsed && activeSidebarTab === tab) {
|
|
2479
|
+
setSidebarCollapsed(true);
|
|
2480
|
+
} else {
|
|
2481
|
+
setActiveSidebarTab(tab);
|
|
2482
|
+
setSidebarCollapsed(false);
|
|
2483
|
+
}
|
|
2276
2484
|
};
|
|
2277
2485
|
|
|
2278
2486
|
var openFileFromGitPanel = function openFileFromGitPanel(path, name) {
|
|
@@ -2794,74 +3002,69 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2794
3002
|
React.createElement(
|
|
2795
3003
|
"div",
|
|
2796
3004
|
{ className: "ide-body", id: "ide-body-container" },
|
|
2797
|
-
|
|
3005
|
+
/* Activity bar — always visible, 48px wide */
|
|
3006
|
+
!zenMode && React.createElement(
|
|
2798
3007
|
"div",
|
|
2799
|
-
{ className: "ide-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
"button",
|
|
2860
|
-
{ type: "button", className: "sidebar-strip-btn", title: "Collapse sidebar", onClick: toggleSidebarCollapsed },
|
|
2861
|
-
React.createElement("i", { className: "fas fa-chevron-left" })
|
|
2862
|
-
)
|
|
2863
|
-
),
|
|
2864
|
-
activeSidebarTab === 'explorer' && React.createElement(
|
|
3008
|
+
{ className: "ide-activity-bar" },
|
|
3009
|
+
React.createElement(
|
|
3010
|
+
"div",
|
|
3011
|
+
{ className: "ide-activity-bar-top" },
|
|
3012
|
+
React.createElement(
|
|
3013
|
+
"button",
|
|
3014
|
+
{
|
|
3015
|
+
type: "button",
|
|
3016
|
+
className: "ide-activity-btn" + (!sidebarCollapsed && activeSidebarTab === 'explorer' ? ' active' : ''),
|
|
3017
|
+
title: "Explorer",
|
|
3018
|
+
onClick: function() { handleActivityBarClick('explorer'); }
|
|
3019
|
+
},
|
|
3020
|
+
React.createElement("i", { className: "far fa-folder" })
|
|
3021
|
+
),
|
|
3022
|
+
React.createElement(
|
|
3023
|
+
"button",
|
|
3024
|
+
{
|
|
3025
|
+
type: "button",
|
|
3026
|
+
className: "ide-activity-btn" + (!sidebarCollapsed && activeSidebarTab === 'search' ? ' active' : ''),
|
|
3027
|
+
title: "Search",
|
|
3028
|
+
onClick: function() { handleActivityBarClick('search'); }
|
|
3029
|
+
},
|
|
3030
|
+
React.createElement("i", { className: "fas fa-search" })
|
|
3031
|
+
),
|
|
3032
|
+
React.createElement(
|
|
3033
|
+
"button",
|
|
3034
|
+
{
|
|
3035
|
+
type: "button",
|
|
3036
|
+
className: "ide-activity-btn" + (!sidebarCollapsed && activeSidebarTab === 'rails' ? ' active' : ''),
|
|
3037
|
+
title: "Rails",
|
|
3038
|
+
onClick: function() { handleActivityBarClick('rails'); }
|
|
3039
|
+
},
|
|
3040
|
+
React.createElement("i", { className: "far fa-gem" })
|
|
3041
|
+
)
|
|
3042
|
+
),
|
|
3043
|
+
React.createElement(
|
|
3044
|
+
"div",
|
|
3045
|
+
{ className: "ide-activity-bar-bottom" },
|
|
3046
|
+
React.createElement(
|
|
3047
|
+
"button",
|
|
3048
|
+
{
|
|
3049
|
+
type: "button",
|
|
3050
|
+
className: "ide-activity-btn" + (activeTab && activeTab.isSettings ? ' active' : ''),
|
|
3051
|
+
title: "Editor Preferences",
|
|
3052
|
+
onClick: openSettingsTab
|
|
3053
|
+
},
|
|
3054
|
+
React.createElement("i", { className: "fas fa-cog" })
|
|
3055
|
+
)
|
|
3056
|
+
)
|
|
3057
|
+
),
|
|
3058
|
+
/* Panel content — shown when not collapsed and not in zen mode */
|
|
3059
|
+
!sidebarCollapsed && !zenMode && React.createElement(
|
|
3060
|
+
"div",
|
|
3061
|
+
{ className: "ide-sidebar", style: { width: sidebarWidth + "px" } },
|
|
3062
|
+
React.createElement("div", { className: "sidebar-panel-title" },
|
|
3063
|
+
activeSidebarTab === 'explorer' ? 'Explorer' :
|
|
3064
|
+
activeSidebarTab === 'search' ? 'Search' :
|
|
3065
|
+
activeSidebarTab === 'rails' ? 'Rails' : ''
|
|
3066
|
+
),
|
|
3067
|
+
activeSidebarTab === 'explorer' && React.createElement(
|
|
2865
3068
|
"div",
|
|
2866
3069
|
{ className: "ide-sidebar-content" },
|
|
2867
3070
|
React.createElement(
|
|
@@ -3226,10 +3429,84 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3226
3429
|
)
|
|
3227
3430
|
);
|
|
3228
3431
|
})()
|
|
3432
|
+
),
|
|
3433
|
+
activeSidebarTab === 'rails' && React.createElement(
|
|
3434
|
+
"div",
|
|
3435
|
+
{ className: "rails-panel" },
|
|
3436
|
+
(function() {
|
|
3437
|
+
var labels = Object.keys(railsFilesMap).sort();
|
|
3438
|
+
if (labels.length === 0) {
|
|
3439
|
+
return React.createElement("div", { className: "rails-panel-empty" }, "Open a Rails file to see related files.");
|
|
3440
|
+
}
|
|
3441
|
+
var sections = labels.map(function(label) {
|
|
3442
|
+
var entry = railsFilesMap[label];
|
|
3443
|
+
var files = entry && entry.files;
|
|
3444
|
+
var loading = entry && entry.loading;
|
|
3445
|
+
if (loading && !files) {
|
|
3446
|
+
return React.createElement("div", { key: label + '_loading', className: "rails-panel-loading" },
|
|
3447
|
+
React.createElement("i", { className: "fas fa-spinner fa-spin" }),
|
|
3448
|
+
" Loading…"
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
if (!files || Object.keys(files).length === 0) return null;
|
|
3452
|
+
var allFiles = [];
|
|
3453
|
+
['model', 'controller', 'helper', 'tests', 'views'].forEach(function(key) {
|
|
3454
|
+
var group = files[key];
|
|
3455
|
+
if (group && group.length) allFiles = allFiles.concat(group);
|
|
3456
|
+
});
|
|
3457
|
+
var customGroups = files['custom'];
|
|
3458
|
+
if (customGroups && typeof customGroups === 'object') {
|
|
3459
|
+
Object.keys(customGroups).forEach(function(base) {
|
|
3460
|
+
var grpFiles = customGroups[base];
|
|
3461
|
+
if (grpFiles && grpFiles.length) allFiles = allFiles.concat(grpFiles);
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
if (allFiles.length === 0) return null;
|
|
3465
|
+
return React.createElement(
|
|
3466
|
+
CollapsibleSection,
|
|
3467
|
+
{
|
|
3468
|
+
key: label,
|
|
3469
|
+
title: label.toUpperCase(),
|
|
3470
|
+
isCollapsed: !!railsGroupsCollapsed[label],
|
|
3471
|
+
onToggle: (function(captured) { return function(isCollapsed) {
|
|
3472
|
+
setRailsGroupsCollapsed(function(prev) {
|
|
3473
|
+
var next = Object.assign({}, prev);
|
|
3474
|
+
next[captured] = isCollapsed;
|
|
3475
|
+
return next;
|
|
3476
|
+
});
|
|
3477
|
+
}; })(label)
|
|
3478
|
+
},
|
|
3479
|
+
React.createElement(
|
|
3480
|
+
"div",
|
|
3481
|
+
null,
|
|
3482
|
+
allFiles.map(function(f) {
|
|
3483
|
+
return React.createElement(
|
|
3484
|
+
"div", {
|
|
3485
|
+
key: f.path,
|
|
3486
|
+
className: "rails-group-item",
|
|
3487
|
+
onClick: (function(file) { return function() { handleSelectFile(file.path, file.name); }; })(f),
|
|
3488
|
+
title: f.path
|
|
3489
|
+
},
|
|
3490
|
+
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(f.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
3491
|
+
React.createElement("span", { className: "rails-group-item-name" }, f.name),
|
|
3492
|
+
dirtyPaths[f.path] && React.createElement("span", { className: "rails-group-item-dirty" }, "●")
|
|
3493
|
+
);
|
|
3494
|
+
})
|
|
3495
|
+
)
|
|
3496
|
+
);
|
|
3497
|
+
});
|
|
3498
|
+
if (railsOverflow > 0) {
|
|
3499
|
+
sections = sections.concat([React.createElement(
|
|
3500
|
+
"div", { key: '__overflow', className: "rails-panel-overflow" },
|
|
3501
|
+
"+" + railsOverflow + " more — close tabs to show all"
|
|
3502
|
+
)]);
|
|
3503
|
+
}
|
|
3504
|
+
return sections;
|
|
3505
|
+
})()
|
|
3229
3506
|
)
|
|
3230
|
-
)
|
|
3231
|
-
|
|
3232
|
-
React.createElement("div", {
|
|
3507
|
+
),
|
|
3508
|
+
/* Sidebar resize divider — only when panel is open */
|
|
3509
|
+
!sidebarCollapsed && !zenMode && React.createElement("div", {
|
|
3233
3510
|
className: "panel-divider sidebar-divider " + (activeResizeMode === 'sidebar' ? 'active' : ''),
|
|
3234
3511
|
onMouseDown: startSidebarResize,
|
|
3235
3512
|
role: "separator",
|
|
@@ -84,6 +84,23 @@
|
|
|
84
84
|
return lines.join('\n');
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Return true if sym is a user-assigned window property (not a browser built-in).
|
|
88
|
+
// Uses the same property-descriptor filter as buildWindowGlobalsShim: browser built-ins
|
|
89
|
+
// are either non-configurable or accessor properties (have a getter), so plain writable
|
|
90
|
+
// configurable data properties reliably identify user-assigned globals.
|
|
91
|
+
function isRuntimeWindowGlobal(sym) {
|
|
92
|
+
if (!sym || typeof window === 'undefined') return false;
|
|
93
|
+
try {
|
|
94
|
+
if (!Object.prototype.hasOwnProperty.call(window, sym)) return false;
|
|
95
|
+
var val;
|
|
96
|
+
try { val = window[sym]; } catch (e) { return false; }
|
|
97
|
+
if (val === null || val === undefined) return false;
|
|
98
|
+
var desc = Object.getOwnPropertyDescriptor(window, sym);
|
|
99
|
+
if (!desc) return false;
|
|
100
|
+
return desc.configurable === true && desc.writable === true && !desc.get;
|
|
101
|
+
} catch (e) { return false; }
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
// Declare a discovered global in Monaco's extra libs so the TS2304 warning disappears.
|
|
88
105
|
// Calling addExtraLib with the same URI replaces the previous content in-place.
|
|
89
106
|
function addDiscoveredGlobal(name) {
|
|
@@ -104,7 +121,10 @@
|
|
|
104
121
|
return FileService.getJsDefinition(word)
|
|
105
122
|
.then(function(data) {
|
|
106
123
|
var results = data && data.results;
|
|
107
|
-
if (!results || !results.length)
|
|
124
|
+
if (!results || !results.length) {
|
|
125
|
+
if (isRuntimeWindowGlobal(word)) addDiscoveredGlobal(word);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
108
128
|
var r = results[0];
|
|
109
129
|
// Only declare as a global when the definition lives in a different file.
|
|
110
130
|
// Locally-defined functions/classes must not get a duplicate declare var.
|
|
@@ -658,6 +678,8 @@
|
|
|
658
678
|
var results = data && data.results;
|
|
659
679
|
if (results && results.length && results[0].file !== modelPath) {
|
|
660
680
|
addDiscoveredGlobal(sym);
|
|
681
|
+
} else if (!results || !results.length) {
|
|
682
|
+
if (isRuntimeWindowGlobal(sym)) addDiscoveredGlobal(sym);
|
|
661
683
|
}
|
|
662
684
|
})
|
|
663
685
|
.catch(function() {});
|
|
@@ -1187,15 +1209,14 @@
|
|
|
1187
1209
|
});
|
|
1188
1210
|
|
|
1189
1211
|
// JS/JSX hover provider: looks up workspace definitions for window globals.
|
|
1190
|
-
//
|
|
1191
|
-
// almost always browser builtins or local vars).
|
|
1212
|
+
// Fires for mixed-case identifiers and for any symbol already in discoveredJsGlobals.
|
|
1192
1213
|
var JS_HOVER_CACHE_TTL_MS = 60000;
|
|
1193
1214
|
monaco.languages.registerHoverProvider('javascript', {
|
|
1194
1215
|
provideHover: function(model, position, token) {
|
|
1195
1216
|
var wordInfo = model.getWordAtPosition(position);
|
|
1196
1217
|
if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return null;
|
|
1197
1218
|
var word = wordInfo.word;
|
|
1198
|
-
if (!/[A-Z]/.test(word)) return null;
|
|
1219
|
+
if (!discoveredJsGlobals[word] && !/[A-Z]/.test(word)) return null;
|
|
1199
1220
|
if (typeof FileService === 'undefined' || !FileService.getJsDefinition) return null;
|
|
1200
1221
|
|
|
1201
1222
|
var cached = jsHoverCache[word];
|
|
@@ -1212,6 +1233,13 @@
|
|
|
1212
1233
|
if (token && token.isCancellationRequested) return null;
|
|
1213
1234
|
var results = data && data.results;
|
|
1214
1235
|
if (!results || !results.length) {
|
|
1236
|
+
if (isRuntimeWindowGlobal(word)) {
|
|
1237
|
+
addDiscoveredGlobal(word);
|
|
1238
|
+
var kind = typeof window[word];
|
|
1239
|
+
var rtValue = { contents: [{ value: '**' + word + '** — runtime global (`' + kind + '`)' }] };
|
|
1240
|
+
jsHoverCache[word] = { ts: Date.now(), value: rtValue };
|
|
1241
|
+
return rtValue;
|
|
1242
|
+
}
|
|
1215
1243
|
jsHoverCache[word] = { ts: Date.now(), value: null };
|
|
1216
1244
|
return null;
|
|
1217
1245
|
}
|
|
@@ -177,7 +177,6 @@ var FileService = (function () {
|
|
|
177
177
|
prefetchCache.delete(path);
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
|
-
prefetchCache.delete(path);
|
|
181
180
|
return entry.promise;
|
|
182
181
|
}
|
|
183
182
|
|
|
@@ -214,6 +213,11 @@ var FileService = (function () {
|
|
|
214
213
|
return axios.get(window.mbeditorBasePath() + '/unused_methods', config).then(function(res) { return res.data; });
|
|
215
214
|
}
|
|
216
215
|
|
|
216
|
+
function getRelatedFiles(path) {
|
|
217
|
+
return axios.get(window.mbeditorBasePath() + '/related_files', { params: { path: path } })
|
|
218
|
+
.then(function(res) { return res.data; });
|
|
219
|
+
}
|
|
220
|
+
|
|
217
221
|
return {
|
|
218
222
|
getWorkspace: getWorkspace,
|
|
219
223
|
getTree: getTree,
|
|
@@ -242,6 +246,7 @@ var FileService = (function () {
|
|
|
242
246
|
cancelPrefetch: cancelPrefetch,
|
|
243
247
|
getModuleMembers: getModuleMembers,
|
|
244
248
|
getFileIncludes: getFileIncludes,
|
|
245
|
-
getUnusedMethods: getUnusedMethods
|
|
249
|
+
getUnusedMethods: getUnusedMethods,
|
|
250
|
+
getRelatedFiles: getRelatedFiles
|
|
246
251
|
};
|
|
247
252
|
})();
|