mbeditor 0.1.6 → 0.1.7

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.

Potentially problematic release.


This version of mbeditor might be problematic. Click here for more details.

@@ -23,7 +23,14 @@ var DEFAULT_EDITOR_PREFS = {
23
23
  fontSize: 13,
24
24
  fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
25
25
  tabSize: 4,
26
- insertSpaces: false
26
+ insertSpaces: false,
27
+ wordWrap: 'off',
28
+ lineNumbers: 'on',
29
+ renderWhitespace: 'none',
30
+ scrollBeyondLastLine: false,
31
+ minimap: false,
32
+ bracketPairColorization: true,
33
+ autoRevealInExplorer: true
27
34
  };
28
35
 
29
36
  var SidebarActionButton = function SidebarActionButton(_ref) {
@@ -260,6 +267,36 @@ var MbeditorApp = function MbeditorApp() {
260
267
  var redmineEnabled = _useState18f2[0];
261
268
  var setRedmineEnabled = _useState18f2[1];
262
269
 
270
+ var _useState18t = useState(false);
271
+ var _useState18t2 = _slicedToArray(_useState18t, 2);
272
+ var testAvailable = _useState18t2[0];
273
+ var setTestAvailable = _useState18t2[1];
274
+
275
+ var _useState18u = useState(null);
276
+ var _useState18u2 = _slicedToArray(_useState18u, 2);
277
+ var testResult = _useState18u2[0];
278
+ var setTestResult = _useState18u2[1];
279
+
280
+ var _useState18v = useState(false);
281
+ var _useState18v2 = _slicedToArray(_useState18v, 2);
282
+ var testLoading = _useState18v2[0];
283
+ var setTestLoading = _useState18v2[1];
284
+
285
+ var _useState18w = useState(true);
286
+ var _useState18w2 = _slicedToArray(_useState18w, 2);
287
+ var testInlineVisible = _useState18w2[0];
288
+ var setTestInlineVisible = _useState18w2[1];
289
+
290
+ var _useState18x = useState(null);
291
+ var _useState18x2 = _slicedToArray(_useState18x, 2);
292
+ var testPanelFile = _useState18x2[0];
293
+ var setTestPanelFile = _useState18x2[1];
294
+
295
+ var _useState18y = useState(false);
296
+ var _useState18y2 = _slicedToArray(_useState18y, 2);
297
+ var testPanelOpen = _useState18y2[0];
298
+ var setTestPanelOpen = _useState18y2[1];
299
+
263
300
  var _useState18p = useState(DEFAULT_EDITOR_PREFS);
264
301
  var _useState18p2 = _slicedToArray(_useState18p, 2);
265
302
  var editorPrefs = _useState18p2[0];
@@ -490,6 +527,9 @@ var MbeditorApp = function MbeditorApp() {
490
527
  if (workspace && typeof workspace.redmineEnabled === 'boolean') {
491
528
  setRedmineEnabled(workspace.redmineEnabled);
492
529
  }
530
+ if (workspace && typeof workspace.testAvailable === 'boolean') {
531
+ setTestAvailable(workspace.testAvailable);
532
+ }
493
533
  });
494
534
  GitService.fetchStatus();
495
535
 
@@ -499,11 +539,20 @@ var MbeditorApp = function MbeditorApp() {
499
539
  if (savedState && savedState.openTabs) {
500
540
  panesToLoad = [{ id: 1, tabs: savedState.openTabs, activeTabId: savedState.activeTabId }, { id: 2, tabs: [], activeTabId: null }];
501
541
  }
542
+ if (savedState && savedState.editorPrefs && typeof savedState.editorPrefs === 'object') {
543
+ setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS, savedState.editorPrefs));
544
+ }
545
+ if (savedState && savedState.activeSidebarTab) {
546
+ setActiveSidebarTab(savedState.activeSidebarTab);
547
+ }
502
548
  if (panesToLoad && panesToLoad.length > 0) {
503
549
  var allTabs = panesToLoad.flatMap(function (p) {
504
550
  return p.tabs;
505
551
  });
506
552
  Promise.all(allTabs.map(function (t) {
553
+ if (t.isSettings || t.path === '__settings__') {
554
+ return Promise.resolve({ content: '' });
555
+ }
507
556
  if (t.isDiff && t.repoPath) {
508
557
  return GitService.fetchDiff(t.repoPath, t.diffBaseSha, t.diffHeadSha)
509
558
  .then(function (d) { return { content: 'Diff loaded', diffOriginal: d.original || '', diffModified: d.modified || '', _isDiffResult: true }; })
@@ -543,9 +592,6 @@ var MbeditorApp = function MbeditorApp() {
543
592
  if (typeof savedState.gitPanelWidth === 'number') {
544
593
  setGitPanelWidth(savedState.gitPanelWidth);
545
594
  }
546
- if (savedState.editorPrefs && typeof savedState.editorPrefs === 'object') {
547
- setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS, savedState.editorPrefs));
548
- }
549
595
  });
550
596
  }
551
597
  });
@@ -827,13 +873,14 @@ var MbeditorApp = function MbeditorApp() {
827
873
  return {
828
874
  id: p.id,
829
875
  activeTabId: p.activeTabId,
830
- tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.isSettings; }).map(function (t) {
876
+ tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff; }).map(function (t) {
831
877
  return {
832
878
  id: t.id,
833
879
  path: t.path,
834
880
  name: t.name,
835
881
  dirty: t.dirty,
836
882
  viewState: t.viewState,
883
+ isSettings: !!t.isSettings,
837
884
  isPreview: !!t.isPreview,
838
885
  previewFor: t.previewFor || null,
839
886
  isDiff: !!t.isDiff,
@@ -844,12 +891,12 @@ var MbeditorApp = function MbeditorApp() {
844
891
  })
845
892
  };
846
893
  });
847
- FileService.saveState({ panes: lightweightPanes, focusedPaneId: st.focusedPaneId, collapsedSections: collapsedSections, expandedDirs: expandedDirs, showGitPanel: showGitPanel, gitPanelWidth: gitPanelWidth, editorPrefs: editorPrefs });
894
+ FileService.saveState({ panes: lightweightPanes, focusedPaneId: st.focusedPaneId, collapsedSections: collapsedSections, expandedDirs: expandedDirs, showGitPanel: showGitPanel, gitPanelWidth: gitPanelWidth, editorPrefs: editorPrefs, activeSidebarTab: activeSidebarTab });
848
895
  }, 1000);
849
896
  return function () {
850
897
  return clearTimeout(timeoutId);
851
898
  };
852
- }, [state.panes, state.focusedPaneId, collapsedSections, expandedDirs, showGitPanel, gitPanelWidth, editorPrefs]);
899
+ }, [state.panes, state.focusedPaneId, collapsedSections, expandedDirs, showGitPanel, gitPanelWidth, editorPrefs, activeSidebarTab]);
853
900
 
854
901
  useEffect(function() {
855
902
  document.documentElement.setAttribute('data-theme', editorPrefs.theme || 'vs-dark');
@@ -1113,6 +1160,39 @@ var MbeditorApp = function MbeditorApp() {
1113
1160
  }
1114
1161
  };
1115
1162
 
1163
+ var handleRunTest = function handleRunTest() {
1164
+ if (!activeTab || !activeTab.path) return;
1165
+ if (testLoading) return;
1166
+
1167
+ setTestLoading(true);
1168
+ EditorStore.setStatus('Running tests...', 'info');
1169
+
1170
+ FileService.runTests(activeTab.path).then(function (res) {
1171
+ setTestResult(res);
1172
+ setTestPanelFile(res.testFile || activeTab.path);
1173
+ setTestPanelOpen(true);
1174
+ if (res.ok) {
1175
+ var s = res.summary || {};
1176
+ var failCount = (s.failed || 0) + (s.errored || 0);
1177
+ if (failCount === 0) {
1178
+ EditorStore.setStatus('All ' + (s.total || 0) + ' tests passed', 'success');
1179
+ } else {
1180
+ EditorStore.setStatus(failCount + ' test' + (failCount === 1 ? '' : 's') + ' failed out of ' + (s.total || 0), 'warning');
1181
+ }
1182
+ } else {
1183
+ EditorStore.setStatus('Test run failed: ' + (res.error || 'unknown error'), 'error');
1184
+ }
1185
+ })["catch"](function (err) {
1186
+ var msg = err.response && err.response.data && err.response.data.error || err.message;
1187
+ setTestResult({ ok: false, error: msg, tests: [], summary: null });
1188
+ setTestPanelFile(activeTab.path);
1189
+ setTestPanelOpen(true);
1190
+ EditorStore.setStatus('Test run failed: ' + msg, 'error');
1191
+ })["finally"](function () {
1192
+ setTestLoading(false);
1193
+ });
1194
+ };
1195
+
1116
1196
  var onFormatRef = useRef(handleFormat);
1117
1197
  onFormatRef.current = handleFormat;
1118
1198
 
@@ -1555,6 +1635,18 @@ var MbeditorApp = function MbeditorApp() {
1555
1635
  },
1556
1636
  onShowHistory: function (path) {
1557
1637
  setHistoryPanelPath(path);
1638
+ },
1639
+ onRevealInExplorer: function (path) {
1640
+ setActiveSidebarTab('explorer');
1641
+ setSelectedTreeNode({ path: path, name: path.split('/').pop(), type: 'file' });
1642
+ setExpandedDirs(function (prev) {
1643
+ var parts = path.split('/');
1644
+ var updates = {};
1645
+ for (var i = 0; i < parts.length - 1; i++) {
1646
+ updates[parts.slice(0, i + 1).join('/')] = true;
1647
+ }
1648
+ return Object.assign({}, prev, updates);
1649
+ });
1558
1650
  }
1559
1651
  });
1560
1652
  };
@@ -1847,7 +1939,7 @@ var MbeditorApp = function MbeditorApp() {
1847
1939
  React.createElement(FileTree, {
1848
1940
  items: treeData,
1849
1941
  onSelect: handleSoftOpenFile,
1850
- activePath: activeTab && activeTab.path,
1942
+ activePath: editorPrefs.autoRevealInExplorer !== false ? (activeTab && activeTab.path) : null,
1851
1943
  selectedPath: selectedTreePath,
1852
1944
  onNodeSelect: setSelectedTreeNode,
1853
1945
  gitFiles: state.gitFiles,
@@ -2079,7 +2171,7 @@ var MbeditorApp = function MbeditorApp() {
2079
2171
  type: 'checkbox',
2080
2172
  className: 'ide-settings-checkbox',
2081
2173
  checked: !!(editorPrefs.insertSpaces),
2082
- onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { insertSpaces: e.target.checked }); }); }
2174
+ onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { insertSpaces: v }); }); }
2083
2175
  })
2084
2176
  ),
2085
2177
  React.createElement(
@@ -2092,6 +2184,89 @@ var MbeditorApp = function MbeditorApp() {
2092
2184
  onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { fontFamily: e.target.value }); }); }
2093
2185
  })
2094
2186
  ),
2187
+ React.createElement(
2188
+ 'label', { className: 'ide-settings-row' },
2189
+ React.createElement('span', { className: 'ide-settings-label' }, 'Word wrap'),
2190
+ React.createElement(
2191
+ 'select', {
2192
+ className: 'ide-settings-select',
2193
+ value: editorPrefs.wordWrap || 'off',
2194
+ onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordWrap: e.target.value }); }); }
2195
+ },
2196
+ React.createElement('option', { value: 'off' }, 'Off'),
2197
+ React.createElement('option', { value: 'on' }, 'On'),
2198
+ React.createElement('option', { value: 'wordWrapColumn' }, 'Column')
2199
+ )
2200
+ ),
2201
+ React.createElement(
2202
+ 'label', { className: 'ide-settings-row' },
2203
+ React.createElement('span', { className: 'ide-settings-label' }, 'Line numbers'),
2204
+ React.createElement(
2205
+ 'select', {
2206
+ className: 'ide-settings-select',
2207
+ value: editorPrefs.lineNumbers || 'on',
2208
+ onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { lineNumbers: e.target.value }); }); }
2209
+ },
2210
+ React.createElement('option', { value: 'on' }, 'On'),
2211
+ React.createElement('option', { value: 'off' }, 'Off'),
2212
+ React.createElement('option', { value: 'relative' }, 'Relative')
2213
+ )
2214
+ ),
2215
+ React.createElement(
2216
+ 'label', { className: 'ide-settings-row' },
2217
+ React.createElement('span', { className: 'ide-settings-label' }, 'Render whitespace'),
2218
+ React.createElement(
2219
+ 'select', {
2220
+ className: 'ide-settings-select',
2221
+ value: editorPrefs.renderWhitespace || 'none',
2222
+ onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderWhitespace: e.target.value }); }); }
2223
+ },
2224
+ React.createElement('option', { value: 'none' }, 'None'),
2225
+ React.createElement('option', { value: 'selection' }, 'Selection only'),
2226
+ React.createElement('option', { value: 'boundary' }, 'Boundary'),
2227
+ React.createElement('option', { value: 'all' }, 'All')
2228
+ )
2229
+ ),
2230
+ React.createElement(
2231
+ 'label', { className: 'ide-settings-row ide-settings-row-check' },
2232
+ React.createElement('span', { className: 'ide-settings-label' }, 'Minimap'),
2233
+ React.createElement('input', {
2234
+ type: 'checkbox',
2235
+ className: 'ide-settings-checkbox',
2236
+ checked: !!(editorPrefs.minimap),
2237
+ onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { minimap: v }); }); }
2238
+ })
2239
+ ),
2240
+ React.createElement(
2241
+ 'label', { className: 'ide-settings-row ide-settings-row-check' },
2242
+ React.createElement('span', { className: 'ide-settings-label' }, 'Scroll beyond last line'),
2243
+ React.createElement('input', {
2244
+ type: 'checkbox',
2245
+ className: 'ide-settings-checkbox',
2246
+ checked: !!(editorPrefs.scrollBeyondLastLine),
2247
+ onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { scrollBeyondLastLine: v }); }); }
2248
+ })
2249
+ ),
2250
+ React.createElement(
2251
+ 'label', { className: 'ide-settings-row ide-settings-row-check' },
2252
+ React.createElement('span', { className: 'ide-settings-label' }, 'Bracket colorization'),
2253
+ React.createElement('input', {
2254
+ type: 'checkbox',
2255
+ className: 'ide-settings-checkbox',
2256
+ checked: !!(editorPrefs.bracketPairColorization),
2257
+ onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { bracketPairColorization: v }); }); }
2258
+ })
2259
+ ),
2260
+ React.createElement(
2261
+ 'label', { className: 'ide-settings-row ide-settings-row-check' },
2262
+ React.createElement('span', { className: 'ide-settings-label' }, 'Explorer follows active file'),
2263
+ React.createElement('input', {
2264
+ type: 'checkbox',
2265
+ className: 'ide-settings-checkbox',
2266
+ checked: !!(editorPrefs.autoRevealInExplorer),
2267
+ onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { autoRevealInExplorer: v }); }); }
2268
+ })
2269
+ ),
2095
2270
  React.createElement(
2096
2271
  'button',
2097
2272
  {
@@ -2112,6 +2287,7 @@ var MbeditorApp = function MbeditorApp() {
2112
2287
  original: pActiveTab.diffOriginal || '',
2113
2288
  modified: pActiveTab.diffModified || '',
2114
2289
  isDark: isDiffDark,
2290
+ editorPrefs: editorPrefs,
2115
2291
  onClose: function() { requestCloseTab(pane.id, pActiveTab.id); }
2116
2292
  });
2117
2293
  } else {
@@ -2121,8 +2297,15 @@ var MbeditorApp = function MbeditorApp() {
2121
2297
  paneId: pane.id,
2122
2298
  markers: markers[pActiveTab.id] || [],
2123
2299
  gitAvailable: gitAvailable,
2300
+ testAvailable: testAvailable,
2301
+ testResult: testResult,
2302
+ testPanelFile: testPanelFile,
2303
+ testLoading: testLoading,
2304
+ testInlineVisible: testInlineVisible,
2124
2305
  editorPrefs: editorPrefs,
2125
2306
  onFormat: function() { onFormatRef.current(); },
2307
+ onRunTest: handleRunTest,
2308
+ onShowHistory: function(path) { setHistoryPanelPath(path); },
2126
2309
  onContentChange: function onContentChange(val) {
2127
2310
  var st = EditorStore.getState();
2128
2311
  var cp = st.panes.find(function(p) { return p.id === pane.id; });
@@ -2151,7 +2334,11 @@ var MbeditorApp = function MbeditorApp() {
2151
2334
  {
2152
2335
  className: "ide-pane " + (isFocused ? 'focused' : '') + " " + (isDropTarget ? 'drop-target' : ''),
2153
2336
  style: { flexBasis: flexBasis, flexShrink: 0, flexGrow: 0, display: 'flex', flexDirection: 'column', minWidth: 0 },
2154
- onClickCapture: function () {
2337
+ onClickCapture: function (e) {
2338
+ // Do not steal click events from controls inside the Settings tab.
2339
+ // Focusing the pane in capture phase can rerender before checkbox
2340
+ // change events are processed, making toggles appear stuck.
2341
+ if (e.target && e.target.closest && e.target.closest('.ide-settings-tab-content')) return;
2155
2342
  return TabManager.focusPane(pane.id);
2156
2343
  },
2157
2344
  onDragOver: function (e) {
@@ -2357,12 +2544,35 @@ var MbeditorApp = function MbeditorApp() {
2357
2544
  ),
2358
2545
 
2359
2546
  // File History Panel overlay
2360
- historyPanelPath && React.createElement(window.FileHistoryPanel || FileHistoryPanel, {
2361
- path: historyPanelPath,
2362
- onClose: function () { return setHistoryPanelPath(null); },
2363
- onSelectCommit: function (hash, path) {
2364
- TabManager.openDiffTab(path, path.split('/').pop(), hash + '^', hash, null);
2365
- }
2547
+ historyPanelPath && React.createElement(
2548
+ React.Fragment,
2549
+ null,
2550
+ React.createElement("div", {
2551
+ style: { position: 'fixed', inset: 0, zIndex: 9800, background: 'rgba(0,0,0,0.55)' },
2552
+ onClick: function() { setHistoryPanelPath(null); }
2553
+ }),
2554
+ React.createElement(window.FileHistoryPanel || FileHistoryPanel, {
2555
+ path: historyPanelPath,
2556
+ onClose: function () { return setHistoryPanelPath(null); },
2557
+ onSelectCommit: function (hash, path) {
2558
+ TabManager.openDiffTab(path, path.split('/').pop(), hash + '^', hash, null);
2559
+ }
2560
+ })
2561
+ ),
2562
+
2563
+ // Test Results Panel overlay — closing hides the dialog but keeps testResult for inline markers
2564
+ (testPanelOpen || testLoading) && React.createElement(window.TestResultsPanel || TestResultsPanel, {
2565
+ result: testResult,
2566
+ testFile: testPanelFile,
2567
+ isLoading: testLoading,
2568
+ showInline: testInlineVisible,
2569
+ onToggleInline: function () { setTestInlineVisible(function (prev) { return !prev; }); },
2570
+ onClose: function () { setTestPanelOpen(false); },
2571
+ onOpenTestFile: testPanelFile ? function () {
2572
+ var fileName = testPanelFile.split('/').pop();
2573
+ TabManager.openTab(testPanelFile, fileName);
2574
+ setTestPanelOpen(false);
2575
+ } : null
2366
2576
  }),
2367
2577
 
2368
2578
  // Commit Detail overlay (shown when a commit row is clicked in CommitGraph)
@@ -16,6 +16,7 @@ var TabBar = function TabBar(_ref) {
16
16
  var onTabDragEnd = _ref.onTabDragEnd;
17
17
  var onHardenTab = _ref.onHardenTab;
18
18
  var onShowHistory = _ref.onShowHistory;
19
+ var onRevealInExplorer = _ref.onRevealInExplorer;
19
20
 
20
21
  var containerRef = useRef(null);
21
22
 
@@ -26,6 +27,13 @@ var TabBar = function TabBar(_ref) {
26
27
  var draggingTabId = _useState2[0];
27
28
  var setDraggingTabId = _useState2[1];
28
29
 
30
+ var _useState3 = useState(null);
31
+
32
+ var _useState4 = _slicedToArray(_useState3, 2);
33
+
34
+ var tabContextMenu = _useState4[0];
35
+ var setTabContextMenu = _useState4[1];
36
+
29
37
  var getTabMarkerClass = function getTabMarkerClass(tab) {
30
38
  var tabMarkers = tab.markers || [];
31
39
  if (!tabMarkers.length) return '';
@@ -55,7 +63,18 @@ var TabBar = function TabBar(_ref) {
55
63
  }
56
64
  }, [activeId, tabs]);
57
65
 
66
+ // Close context menu on outside click (bubble phase so onMouseDown on the menu can stop it)
67
+ useEffect(function () {
68
+ if (!tabContextMenu) return;
69
+ var handler = function () { setTabContextMenu(null); };
70
+ document.addEventListener('mousedown', handler);
71
+ return function () { document.removeEventListener('mousedown', handler); };
72
+ }, [tabContextMenu]);
73
+
58
74
  return React.createElement(
75
+ React.Fragment,
76
+ null,
77
+ React.createElement(
59
78
  'div',
60
79
  { className: 'tab-bar', ref: containerRef, onWheel: function (e) {
61
80
  if (containerRef.current) {
@@ -90,7 +109,7 @@ var TabBar = function TabBar(_ref) {
90
109
  onContextMenu: function (e) {
91
110
  if (isSpecial) return;
92
111
  e.preventDefault();
93
- if (onShowHistory) onShowHistory(tab.path);
112
+ setTabContextMenu({ x: e.clientX, y: e.clientY, tab: tab });
94
113
  }
95
114
  },
96
115
  React.createElement('i', { className: 'tab-item-icon ' + (tab.isSettings ? 'fas fa-cog' : (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code')) }),
@@ -117,6 +136,56 @@ var TabBar = function TabBar(_ref) {
117
136
  )
118
137
  );
119
138
  })
139
+ ),
140
+ tabContextMenu && React.createElement(
141
+ 'div',
142
+ {
143
+ className: 'ide-tab-context-menu',
144
+ style: {
145
+ position: 'fixed',
146
+ top: tabContextMenu.y,
147
+ left: tabContextMenu.x,
148
+ zIndex: 9999,
149
+ background: '#252526',
150
+ border: '1px solid #454545',
151
+ borderRadius: '4px',
152
+ boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
153
+ minWidth: '160px',
154
+ padding: '4px 0'
155
+ },
156
+ onMouseDown: function(e) { e.stopPropagation(); }
157
+ },
158
+ onShowHistory && React.createElement(
159
+ 'div',
160
+ {
161
+ className: 'ide-tab-context-menu-item',
162
+ style: { padding: '6px 14px', cursor: 'pointer', color: '#ccc', fontSize: '12px', display: 'flex', alignItems: 'center', gap: '8px' },
163
+ onMouseEnter: function(e) { e.currentTarget.style.background = '#094771'; },
164
+ onMouseLeave: function(e) { e.currentTarget.style.background = 'transparent'; },
165
+ onClick: function() {
166
+ setTabContextMenu(null);
167
+ onShowHistory(tabContextMenu.tab.path);
168
+ }
169
+ },
170
+ React.createElement('i', { className: 'fas fa-history', style: { width: '14px', textAlign: 'center' } }),
171
+ 'File History'
172
+ ),
173
+ onRevealInExplorer && React.createElement(
174
+ 'div',
175
+ {
176
+ className: 'ide-tab-context-menu-item',
177
+ style: { padding: '6px 14px', cursor: 'pointer', color: '#ccc', fontSize: '12px', display: 'flex', alignItems: 'center', gap: '8px' },
178
+ onMouseEnter: function(e) { e.currentTarget.style.background = '#094771'; },
179
+ onMouseLeave: function(e) { e.currentTarget.style.background = 'transparent'; },
180
+ onClick: function() {
181
+ setTabContextMenu(null);
182
+ onRevealInExplorer(tabContextMenu.tab.path);
183
+ }
184
+ },
185
+ React.createElement('i', { className: 'fas fa-sitemap', style: { width: '14px', textAlign: 'center' } }),
186
+ 'Find in Explorer'
187
+ )
188
+ )
120
189
  );
121
190
  };
122
191
 
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ var TestResultsPanel = function TestResultsPanel(_ref) {
4
+ var result = _ref.result;
5
+ var testFile = _ref.testFile;
6
+ var isLoading = _ref.isLoading;
7
+ var onClose = _ref.onClose;
8
+ var showInline = _ref.showInline;
9
+ var onToggleInline = _ref.onToggleInline;
10
+ var onOpenTestFile = _ref.onOpenTestFile;
11
+
12
+ if (!result && !isLoading) return null;
13
+
14
+ var summary = result && result.summary;
15
+ var tests = result && result.tests || [];
16
+ var error = result && result.error;
17
+
18
+ var statusIcon = function statusIcon(status) {
19
+ if (status === 'pass') return React.createElement('i', { className: 'fas fa-check-circle', style: { color: '#4ec9b0', marginRight: '6px' } });
20
+ if (status === 'fail') return React.createElement('i', { className: 'fas fa-times-circle', style: { color: '#f14c4c', marginRight: '6px' } });
21
+ if (status === 'error') return React.createElement('i', { className: 'fas fa-exclamation-circle', style: { color: '#cca700', marginRight: '6px' } });
22
+ if (status === 'skip') return React.createElement('i', { className: 'fas fa-forward', style: { color: '#888', marginRight: '6px' } });
23
+ return React.createElement('i', { className: 'fas fa-circle', style: { color: '#888', marginRight: '6px' } });
24
+ };
25
+
26
+ return React.createElement(
27
+ React.Fragment,
28
+ null,
29
+ React.createElement('div', { className: 'ide-modal-backdrop', onClick: onClose }),
30
+ React.createElement(
31
+ 'div',
32
+ { className: 'ide-modal-panel' },
33
+ React.createElement(
34
+ 'div',
35
+ { className: 'ide-file-history-header' },
36
+ React.createElement(
37
+ 'div',
38
+ { className: 'ide-file-history-title' },
39
+ React.createElement('i', { className: 'fas fa-flask' }),
40
+ React.createElement(
41
+ 'span',
42
+ null,
43
+ 'Tests: ',
44
+ testFile ? testFile.split('/').pop() : 'Results'
45
+ )
46
+ ),
47
+ React.createElement(
48
+ 'div',
49
+ { style: { display: 'flex', alignItems: 'center', gap: '6px' } },
50
+ onToggleInline && React.createElement(
51
+ 'button',
52
+ {
53
+ className: 'ide-icon-btn' + (showInline ? ' active' : ''),
54
+ onClick: onToggleInline,
55
+ title: showInline ? 'Hide inline markers' : 'Show inline markers',
56
+ style: { fontSize: '11px', padding: '2px 5px', opacity: showInline ? 1 : 0.6, background: showInline ? 'rgba(255,255,255,0.1)' : 'transparent', border: 'none', color: '#ccc', cursor: 'pointer', borderRadius: '3px' }
57
+ },
58
+ React.createElement('i', { className: 'fas fa-map-marker-alt' })
59
+ ),
60
+ onOpenTestFile && React.createElement(
61
+ 'button',
62
+ {
63
+ className: 'ide-icon-btn',
64
+ onClick: onOpenTestFile,
65
+ title: 'Open test file',
66
+ style: { fontSize: '11px', padding: '2px 5px', background: 'transparent', border: 'none', color: '#ccc', cursor: 'pointer', borderRadius: '3px' }
67
+ },
68
+ React.createElement('i', { className: 'fas fa-external-link-alt', style: { marginRight: '4px' } }),
69
+ 'Open Test File'
70
+ ),
71
+ React.createElement(
72
+ 'button',
73
+ { className: 'ide-icon-btn', onClick: onClose, title: 'Close Test Results' },
74
+ React.createElement('i', { className: 'fas fa-times' })
75
+ )
76
+ )
77
+ ),
78
+ React.createElement(
79
+ 'div',
80
+ { className: 'ide-file-history-content' },
81
+ isLoading ? React.createElement(
82
+ 'div',
83
+ { className: 'ide-loading-state' },
84
+ React.createElement('i', { className: 'fas fa-spinner fa-spin' }),
85
+ ' Running tests...'
86
+ ) : error ? React.createElement(
87
+ 'div',
88
+ { className: 'ide-error-state' },
89
+ error
90
+ ) : !summary ? React.createElement(
91
+ 'div',
92
+ { className: 'ide-empty-state' },
93
+ 'No test results.'
94
+ ) : React.createElement(
95
+ 'div',
96
+ null,
97
+ React.createElement(
98
+ 'div',
99
+ { style: { padding: '8px 12px', borderBottom: '1px solid #3c3c3c', fontSize: '12px', color: '#ccc', display: 'flex', gap: '12px', flexWrap: 'wrap' } },
100
+ React.createElement('span', null, React.createElement('strong', null, summary.total), ' total'),
101
+ summary.passed > 0 && React.createElement('span', { style: { color: '#4ec9b0' } }, React.createElement('i', { className: 'fas fa-check-circle', style: { marginRight: '3px' } }), summary.passed, ' passed'),
102
+ summary.failed > 0 && React.createElement('span', { style: { color: '#f14c4c' } }, React.createElement('i', { className: 'fas fa-times-circle', style: { marginRight: '3px' } }), summary.failed, ' failed'),
103
+ summary.errored > 0 && React.createElement('span', { style: { color: '#cca700' } }, React.createElement('i', { className: 'fas fa-exclamation-circle', style: { marginRight: '3px' } }), summary.errored, ' errors'),
104
+ summary.skipped > 0 && React.createElement('span', { style: { color: '#888' } }, summary.skipped, ' skipped'),
105
+ summary.duration != null && React.createElement('span', { style: { color: '#888' } }, summary.duration, 's')
106
+ ),
107
+ tests.length > 0 ? React.createElement(
108
+ 'div',
109
+ { className: 'git-list' },
110
+ tests.map(function (t, i) {
111
+ return React.createElement(
112
+ 'div',
113
+ { key: i, className: 'git-commit-item', style: { cursor: 'default' } },
114
+ React.createElement(
115
+ 'div',
116
+ { className: 'git-commit-title', style: { display: 'flex', alignItems: 'center' } },
117
+ statusIcon(t.status),
118
+ React.createElement('span', { title: t.name }, t.name)
119
+ ),
120
+ t.message && React.createElement(
121
+ 'div',
122
+ { className: 'git-commit-meta', style: { color: '#f14c4c', whiteSpace: 'pre-wrap', fontFamily: 'monospace', fontSize: '11px', marginTop: '4px', maxHeight: '120px', overflow: 'auto' } },
123
+ t.message
124
+ ),
125
+ t.line && React.createElement(
126
+ 'div',
127
+ { className: 'git-commit-meta' },
128
+ 'Line ',
129
+ t.line
130
+ )
131
+ );
132
+ })
133
+ ) : React.createElement(
134
+ 'div',
135
+ { style: { padding: '8px 12px', fontSize: '12px', color: '#888' } },
136
+ summary.total > 0 ? 'All tests passed.' : 'No test details available. Check raw output.'
137
+ ),
138
+ result && result.raw && tests.length === 0 && React.createElement(
139
+ 'details',
140
+ { style: { padding: '8px 12px' } },
141
+ React.createElement('summary', { style: { cursor: 'pointer', color: '#888', fontSize: '11px' } }, 'Raw output'),
142
+ React.createElement('pre', { style: { fontSize: '11px', color: '#ccc', whiteSpace: 'pre-wrap', maxHeight: '300px', overflow: 'auto', marginTop: '6px' } }, result.raw)
143
+ )
144
+ )
145
+ )
146
+ )
147
+ );
148
+ };
149
+
150
+ window.TestResultsPanel = TestResultsPanel;
@@ -51,6 +51,10 @@ var FileService = (function () {
51
51
  return axios.post(basePath() + '/format', { path: path }).then(function(res) { return res.data; });
52
52
  }
53
53
 
54
+ function runTests(path) {
55
+ return axios.post(basePath() + '/test', { path: path }).then(function(res) { return res.data; });
56
+ }
57
+
54
58
  function ping() {
55
59
  return axios.get(basePath() + '/ping', { timeout: 4000 }).then(function(res) { return res.data; });
56
60
  }
@@ -75,6 +79,7 @@ var FileService = (function () {
75
79
  lintFile: lintFile,
76
80
  quickFixOffense: quickFixOffense,
77
81
  formatFile: formatFile,
82
+ runTests: runTests,
78
83
  ping: ping,
79
84
  getState: getState,
80
85
  saveState: saveState