mbeditor 0.5.3 → 0.7.1

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +7 -0
  4. data/app/assets/javascripts/mbeditor/application.js +3 -0
  5. data/app/assets/javascripts/mbeditor/components/ChangelogView.js +145 -0
  6. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
  7. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +359 -31
  8. data/app/assets/javascripts/mbeditor/components/FileTree.js +177 -116
  9. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +952 -143
  10. data/app/assets/javascripts/mbeditor/components/TabBar.js +9 -0
  11. data/app/assets/javascripts/mbeditor/conflict_parser.js +48 -0
  12. data/app/assets/javascripts/mbeditor/editor_plugins.js +420 -67
  13. data/app/assets/javascripts/mbeditor/editor_store.js +1 -0
  14. data/app/assets/javascripts/mbeditor/file_service.js +34 -6
  15. data/app/assets/javascripts/mbeditor/git_service.js +2 -1
  16. data/app/assets/javascripts/mbeditor/history_service.js +177 -0
  17. data/app/assets/javascripts/mbeditor/search_service.js +1 -0
  18. data/app/assets/javascripts/mbeditor/tab_manager.js +8 -5
  19. data/app/assets/stylesheets/mbeditor/application.css +112 -0
  20. data/app/assets/stylesheets/mbeditor/editor.css +443 -78
  21. data/app/channels/mbeditor/editor_channel.rb +5 -41
  22. data/app/controllers/mbeditor/application_controller.rb +8 -1
  23. data/app/controllers/mbeditor/editors_controller.rb +276 -654
  24. data/app/controllers/mbeditor/git_controller.rb +2 -61
  25. data/app/services/mbeditor/availability_probe.rb +83 -0
  26. data/app/services/mbeditor/code_search_service.rb +42 -0
  27. data/app/services/mbeditor/editor_state_service.rb +91 -0
  28. data/app/services/mbeditor/exclusion_matcher.rb +23 -0
  29. data/app/services/mbeditor/file_operation_service.rb +68 -0
  30. data/app/services/mbeditor/file_tree_service.rb +69 -0
  31. data/app/services/mbeditor/git_combined_diff_service.rb +43 -0
  32. data/app/services/mbeditor/git_commit_detail_service.rb +46 -0
  33. data/app/services/mbeditor/git_info_service.rb +151 -0
  34. data/app/services/mbeditor/git_service.rb +36 -26
  35. data/app/services/mbeditor/js_definition_service.rb +59 -0
  36. data/app/services/mbeditor/js_members_service.rb +62 -0
  37. data/app/services/mbeditor/process_runner.rb +48 -0
  38. data/app/services/mbeditor/rails_related_files_service.rb +282 -0
  39. data/app/services/mbeditor/ruby_definition_service.rb +77 -101
  40. data/app/services/mbeditor/schema_service.rb +270 -0
  41. data/app/services/mbeditor/search_replace_service.rb +184 -0
  42. data/app/services/mbeditor/test_runner_service.rb +5 -27
  43. data/app/views/layouts/mbeditor/application.html.erb +2 -2
  44. data/config/routes.rb +8 -1
  45. data/lib/mbeditor/configuration.rb +4 -2
  46. data/lib/mbeditor/version.rb +1 -1
  47. data/public/monaco-editor/vs/language/css/cssMode.js +13 -0
  48. data/public/monaco-editor/vs/language/css/cssWorker.js +77 -0
  49. data/public/monaco-editor/vs/language/html/htmlMode.js +13 -0
  50. data/public/monaco-editor/vs/language/html/htmlWorker.js +454 -0
  51. data/public/monaco-editor/vs/language/json/jsonMode.js +19 -0
  52. data/public/monaco-editor/vs/language/json/jsonWorker.js +42 -0
  53. metadata +26 -3
  54. data/app/services/mbeditor/unused_methods_service.rb +0 -139
@@ -63,18 +63,53 @@ var FileTree = function FileTree(_ref) {
63
63
  // Ref that always points to the latest onNodeSelect prop, avoiding stale closures in the effect.
64
64
  var onNodeSelectRef = useRef(onNodeSelect);
65
65
  onNodeSelectRef.current = onNodeSelect;
66
+ // Refs that track pending create/rename state for the typeahead guard.
67
+ // Since these props are not in the effect's dependency array, we use refs to
68
+ // always read the current values without stale closures.
69
+ var pendingCreateRef = useRef(pendingCreate);
70
+ var pendingRenameRef = useRef(pendingRename);
71
+ pendingCreateRef.current = pendingCreate;
72
+ pendingRenameRef.current = pendingRename;
66
73
  // Tracks whether the user's most recent mousedown was inside the sidebar.
67
74
  // Monaco re-steals keyboard focus after any click so e.target on keydown is
68
75
  // always Monaco's textarea — the only reliable "is the user in the explorer?"
69
76
  // signal is where they last clicked.
70
77
  var sidebarActiveRef = useRef(false);
71
78
 
79
+ // Virtual scroll state
80
+ var ROW_HEIGHT = 22;
81
+ var BUFFER = 5;
82
+ var flatItemsRef = useRef([]);
83
+ var scrollParentRef = useRef(null);
84
+
85
+ var _scrollState = useState(0);
86
+ var scrollTop = _scrollState[0];
87
+ var setScrollTop = _scrollState[1];
88
+
89
+ var _heightState = useState(400);
90
+ var containerHeight = _heightState[0];
91
+ var setContainerHeight = _heightState[1];
92
+
93
+ // Scroll item at idx into view via the parent scroll container
94
+ var scrollToItem = function scrollToItem(idx) {
95
+ var parent = scrollParentRef.current;
96
+ if (!parent || !containerRef.current) return;
97
+ var parentTop = parent.getBoundingClientRect().top;
98
+ var treeTop = containerRef.current.getBoundingClientRect().top;
99
+ var treeOffset = parent.scrollTop + (treeTop - parentTop);
100
+ var itemAbsTop = treeOffset + idx * ROW_HEIGHT;
101
+ var itemAbsBottom = itemAbsTop + ROW_HEIGHT;
102
+ if (itemAbsTop < parent.scrollTop || itemAbsBottom > parent.scrollTop + parent.clientHeight) {
103
+ parent.scrollTop = Math.max(0, itemAbsTop - Math.floor((parent.clientHeight - ROW_HEIGHT) / 2));
104
+ }
105
+ };
106
+
72
107
  // Scroll the highlighted node into view when anchorPath changes (e.g. Find in Explorer)
73
108
  useEffect(function () {
74
- if (!anchorPath || !containerRef.current) return;
109
+ if (!anchorPath) return;
75
110
  var timer = setTimeout(function () {
76
- var el = containerRef.current && containerRef.current.querySelector('.tree-item.selected');
77
- if (el) el.scrollIntoView({ block: 'center' });
111
+ var idx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'node' && item.node.path === anchorPath; });
112
+ if (idx >= 0) scrollToItem(idx);
78
113
  }, 60);
79
114
  return function () { clearTimeout(timer); };
80
115
  }, [anchorPath]);
@@ -97,15 +132,8 @@ var FileTree = function FileTree(_ref) {
97
132
 
98
133
  // After the DOM updates, scroll the active item into view only if not already visible
99
134
  var timer = setTimeout(function () {
100
- if (!containerRef.current) return;
101
- var el = containerRef.current.querySelector('.tree-item.active');
102
- if (el) {
103
- var elRect = el.getBoundingClientRect();
104
- var containerRect = containerRef.current.getBoundingClientRect();
105
- if (elRect.top < containerRect.top || elRect.bottom > containerRect.bottom) {
106
- el.scrollIntoView({ block: 'nearest' });
107
- }
108
- }
135
+ var idx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'node' && item.node.path === activePath; });
136
+ if (idx >= 0) scrollToItem(idx);
109
137
  }, 80);
110
138
  return function () { clearTimeout(timer); };
111
139
  }, [activePath]);
@@ -134,6 +162,8 @@ var FileTree = function FileTree(_ref) {
134
162
  if (pendingCreate) {
135
163
  setInlineValue('');
136
164
  committedRef.current = false;
165
+ var createIdx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'create'; });
166
+ if (createIdx >= 0) scrollToItem(createIdx);
137
167
  setTimeout(function () {
138
168
  if (inlineRef.current) inlineRef.current.focus();
139
169
  }, 0);
@@ -146,6 +176,35 @@ var FileTree = function FileTree(_ref) {
146
176
  return function() { clearTimeout(hoverTimerRef.current); };
147
177
  }, []);
148
178
 
179
+ // Track the parent scroll container (.ide-sidebar-scrollable) for virtual scroll.
180
+ // The file-tree-root itself does not scroll — the parent does.
181
+ useEffect(function() {
182
+ if (!containerRef.current) return;
183
+ var parent = containerRef.current.closest('.ide-sidebar-scrollable');
184
+ if (!parent) return;
185
+ scrollParentRef.current = parent;
186
+
187
+ var update = function() {
188
+ if (!containerRef.current || !parent) return;
189
+ var parentRect = parent.getBoundingClientRect();
190
+ var treeRect = containerRef.current.getBoundingClientRect();
191
+ var treeOffset = parent.scrollTop + (treeRect.top - parentRect.top);
192
+ setScrollTop(Math.max(0, parent.scrollTop - treeOffset));
193
+ setContainerHeight(parent.clientHeight);
194
+ };
195
+
196
+ parent.addEventListener('scroll', update, { passive: true });
197
+ var obs = new ResizeObserver(update);
198
+ obs.observe(parent);
199
+ obs.observe(containerRef.current);
200
+ update();
201
+
202
+ return function() {
203
+ parent.removeEventListener('scroll', update);
204
+ obs.disconnect();
205
+ };
206
+ }, []);
207
+
149
208
  var toggleFolder = function toggleFolder(path, e) {
150
209
  e.stopPropagation();
151
210
  var next = !(expandedDirs && expandedDirs[path]);
@@ -186,22 +245,10 @@ var FileTree = function FileTree(_ref) {
186
245
  };
187
246
 
188
247
  // Returns all visible paths in depth-first render order (for shift+click range select)
189
- var computeVisiblePaths = function computeVisiblePaths(nodes) {
190
- var paths = [];
191
- var visit = function visit(list) {
192
- var sorted = [].concat(_toConsumableArray(list)).sort(function (a, b) {
193
- if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
194
- return a.name.localeCompare(b.name);
195
- });
196
- sorted.forEach(function (node) {
197
- paths.push(node.path);
198
- if (node.type === 'folder' && expandedDirs && expandedDirs[node.path] && node.children) {
199
- visit(node.children);
200
- }
201
- });
202
- };
203
- visit(nodes);
204
- return paths;
248
+ var computeVisiblePaths = function computeVisiblePaths(_nodes) {
249
+ return flatItemsRef.current
250
+ .filter(function(item) { return item.kind === 'node'; })
251
+ .map(function(item) { return item.node.path; });
205
252
  };
206
253
 
207
254
  // Global type-ahead: tracks the last mousedown to know if the user is "in the explorer",
@@ -219,6 +266,8 @@ var FileTree = function FileTree(_ref) {
219
266
  if (!typeaheadEnabled) return;
220
267
  if (!sidebarActiveRef.current) return;
221
268
  if (e.ctrlKey || e.metaKey || e.altKey) return;
269
+ // Prevent typeahead from jumping files while typing a new name in inline create/rename.
270
+ if (pendingCreateRef.current || pendingRenameRef.current) return;
222
271
  if (e.key.length !== 1) return;
223
272
  // Still skip when an inline rename/create input is the actual focused element.
224
273
  if (e.target && e.target.tagName === 'INPUT') return;
@@ -232,17 +281,14 @@ var FileTree = function FileTree(_ref) {
232
281
  }, 600);
233
282
 
234
283
  var prefix = typeaheadBufferRef.current;
235
- var allItems = containerRef.current.querySelectorAll('.tree-item');
236
- for (var i = 0; i < allItems.length; i++) {
237
- var nameEl = allItems[i].querySelector('.tree-item-name');
238
- if (nameEl && nameEl.textContent.trim().toLowerCase().indexOf(prefix) === 0) {
239
- allItems[i].scrollIntoView({ block: 'nearest' });
240
- // Visually select the matched item so the user sees the highlight change.
241
- var nodePath = allItems[i].getAttribute('data-path');
242
- var nodeName = allItems[i].getAttribute('data-name');
243
- var nodeType = allItems[i].getAttribute('data-type');
244
- if (nodePath && onNodeSelectRef.current) {
245
- onNodeSelectRef.current({ path: nodePath, name: nodeName || nodePath.split('/').pop(), type: nodeType || 'file' });
284
+ var flat = flatItemsRef.current;
285
+ for (var i = 0; i < flat.length; i++) {
286
+ var fItem = flat[i];
287
+ if (fItem.kind !== 'node') continue;
288
+ if (fItem.node.name.toLowerCase().indexOf(prefix) === 0) {
289
+ scrollToItem(i);
290
+ if (onNodeSelectRef.current) {
291
+ onNodeSelectRef.current({ path: fItem.node.path, name: fItem.node.name, type: fItem.node.type });
246
292
  }
247
293
  break;
248
294
  }
@@ -348,60 +394,93 @@ var FileTree = function FileTree(_ref) {
348
394
  );
349
395
  };
350
396
 
351
- var renderTree = function renderTree(nodes, folderPath) {
352
- var sortedNodes = [].concat(_toConsumableArray(nodes)).sort(function (a, b) {
353
- if (a.type !== b.type) return a.type === "folder" ? -1 : 1;
397
+ // Flatten the visible tree into a linear array for virtual scrolling.
398
+ var flattenVisible = function flattenVisible(nodes, depth, parentPath) {
399
+ var result = [];
400
+ var sorted = [].concat(_toConsumableArray(nodes)).sort(function(a, b) {
401
+ if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
354
402
  return a.name.localeCompare(b.name);
355
403
  });
404
+ sorted.forEach(function(node) {
405
+ result.push({ kind: 'node', node: node, depth: depth });
406
+ if (node.type === 'folder' && expandedDirs && expandedDirs[node.path] && node.children) {
407
+ var children = flattenVisible(node.children, depth + 1, node.path);
408
+ for (var ci = 0; ci < children.length; ci++) result.push(children[ci]);
409
+ }
410
+ });
411
+ if (pendingCreate && pendingCreate.parentPath === parentPath) {
412
+ result.push({ kind: 'create', depth: depth });
413
+ }
414
+ return result;
415
+ };
416
+
417
+ var flatItems = flattenVisible(items || [], 0, '');
418
+ flatItemsRef.current = flatItems;
419
+
420
+ var totalHeight = flatItems.length * ROW_HEIGHT;
421
+ var startIdx = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER);
422
+ var endIdx = Math.min(flatItems.length - 1, Math.ceil((scrollTop + containerHeight) / ROW_HEIGHT) + BUFFER);
356
423
 
357
- var rows = sortedNodes.map(function (node) {
358
- var isFolder = node.type === "folder";
359
- var isExpanded = !!(expandedDirs && expandedDirs[node.path]);
360
- var isRenamingThisNode = !!(pendingRename && pendingRename.path === node.path);
361
- var isOpenFile = activePath === node.path;
362
- var isSelected = !!(selectedPaths && selectedPaths.has(node.path));
363
- var isDragOver = isFolder && dragOverFolder === node.path;
364
- var status = getGitStatus(node.path);
365
- var statusMeta = getTreeStatusMeta(status);
366
- var isModified = statusMeta && (statusMeta.cssKey === "M" || statusMeta.cssKey === "A");
367
-
368
- var classNames = 'tree-item' +
369
- (isOpenFile ? ' active' : '') +
370
- (isSelected ? ' selected' : '') +
371
- (isModified ? ' modified' : '') +
372
- (isDragOver ? ' drag-over' : '');
424
+ var renderRow = function renderRow(item, idx) {
425
+ var indentPx = 8 + item.depth * 12;
373
426
 
427
+ if (item.kind === 'create') {
374
428
  return React.createElement(
375
429
  'div',
376
- { key: node.path, className: 'file-tree' },
377
- isRenamingThisNode ? renderInlineRenameRow(node) : React.createElement(
430
+ { key: '__inline-create__', style: { position: 'absolute', top: idx * ROW_HEIGHT, left: 0, right: 0 } },
431
+ React.createElement('div', { style: { paddingLeft: indentPx } }, renderInlineRow())
432
+ );
433
+ }
434
+
435
+ var node = item.node;
436
+ var isFolder = node.type === 'folder';
437
+ var isExpanded = !!(expandedDirs && expandedDirs[node.path]);
438
+ var isRenamingThisNode = !!(pendingRename && pendingRename.path === node.path);
439
+ var isOpenFile = activePath === node.path;
440
+ var isSelected = !!(selectedPaths && selectedPaths.has(node.path));
441
+ var isDragOver = isFolder && dragOverFolder === node.path;
442
+ var status = getGitStatus(node.path);
443
+ var statusMeta = getTreeStatusMeta(status);
444
+ var isModified = statusMeta && (statusMeta.cssKey === 'M' || statusMeta.cssKey === 'A');
445
+
446
+ var classNames = 'tree-item' +
447
+ (isOpenFile ? ' active' : '') +
448
+ (isSelected ? ' selected' : '') +
449
+ (isModified ? ' modified' : '') +
450
+ (isDragOver ? ' drag-over' : '');
451
+
452
+ return React.createElement(
453
+ 'div',
454
+ { key: node.path, style: { position: 'absolute', top: idx * ROW_HEIGHT, left: 0, right: 0 } },
455
+ isRenamingThisNode
456
+ ? React.createElement('div', { style: { paddingLeft: indentPx } }, renderInlineRenameRow(node))
457
+ : React.createElement(
378
458
  'div',
379
459
  {
380
460
  className: classNames,
461
+ style: { paddingLeft: indentPx },
381
462
  'data-path': node.path,
382
463
  'data-name': node.name,
383
464
  'data-type': node.type,
384
465
  draggable: true,
385
- onDragStart: function (e) {
386
- // If the dragged node is part of a multi-selection, drag all selected; otherwise just this node
466
+ onDragStart: function(e) {
387
467
  var srcPaths = (selectedPaths && selectedPaths.has(node.path) && selectedPaths.size > 1)
388
468
  ? Array.from(selectedPaths)
389
469
  : [node.path];
390
470
  e.dataTransfer.setData('text/plain', JSON.stringify(srcPaths));
391
471
  e.dataTransfer.effectAllowed = 'move';
392
472
  },
393
- onDragOver: function (e) {
473
+ onDragOver: function(e) {
394
474
  if (!isFolder) return;
395
475
  e.preventDefault();
396
476
  e.stopPropagation();
397
477
  e.dataTransfer.dropEffect = 'move';
398
478
  if (dragOverFolder !== node.path) setDragOverFolder(node.path);
399
479
  },
400
- onDragLeave: function (e) {
401
- // Only clear if we're leaving the folder item itself, not entering a child
480
+ onDragLeave: function() {
402
481
  if (dragOverFolder === node.path) setDragOverFolder(null);
403
482
  },
404
- onDrop: function (e) {
483
+ onDrop: function(e) {
405
484
  e.preventDefault();
406
485
  e.stopPropagation();
407
486
  setDragOverFolder(null);
@@ -411,66 +490,51 @@ var FileTree = function FileTree(_ref) {
411
490
  if (onMove && srcPaths && srcPaths.length > 0) onMove(srcPaths, node.path);
412
491
  } catch (err) {}
413
492
  },
414
- onDragEnd: function () { setDragOverFolder(null); },
415
- onClick: function (e) {
493
+ onDragEnd: function() { setDragOverFolder(null); },
494
+ onClick: function(e) {
416
495
  if (e.ctrlKey || e.metaKey) {
417
- // Ctrl/Cmd+click: toggle this node in/out of selection
418
496
  if (onMultiSelect) {
419
497
  var newPaths = new Set(selectedPaths || []);
420
- if (newPaths.has(node.path)) {
421
- newPaths.delete(node.path);
422
- } else {
423
- newPaths.add(node.path);
424
- }
498
+ if (newPaths.has(node.path)) { newPaths.delete(node.path); } else { newPaths.add(node.path); }
425
499
  onMultiSelect(newPaths);
426
500
  }
427
- // Don't open file on ctrl-click
428
501
  } else if (e.shiftKey && anchorPath) {
429
- // Shift+click: range-select from anchor to this node
430
502
  if (onMultiSelect) {
431
503
  var visiblePaths = computeVisiblePaths(items);
432
- var anchorIdx = visiblePaths.indexOf(anchorPath);
433
- var currentIdx = visiblePaths.indexOf(node.path);
434
- if (anchorIdx >= 0 && currentIdx >= 0) {
435
- var start = Math.min(anchorIdx, currentIdx);
436
- var end = Math.max(anchorIdx, currentIdx);
504
+ var anchorIdx2 = visiblePaths.indexOf(anchorPath);
505
+ var currentIdx2 = visiblePaths.indexOf(node.path);
506
+ if (anchorIdx2 >= 0 && currentIdx2 >= 0) {
507
+ var start = Math.min(anchorIdx2, currentIdx2);
508
+ var end = Math.max(anchorIdx2, currentIdx2);
437
509
  onMultiSelect(new Set(visiblePaths.slice(start, end + 1)));
438
510
  } else {
439
511
  selectNode(node);
440
512
  }
441
513
  }
442
514
  } else {
443
- // Normal single click
444
515
  selectNode(node);
445
- if (isFolder) {
446
- toggleFolder(node.path, e);
447
- } else {
448
- onSelect(node.path, node.name);
449
- }
516
+ if (isFolder) { toggleFolder(node.path, e); } else { onSelect(node.path, node.name); }
450
517
  if (containerRef.current) containerRef.current.focus();
451
518
  }
452
519
  },
453
- onDoubleClick: function (e) {
454
- if (!isFolder && onFileDoubleClick) {
455
- e.stopPropagation();
456
- onFileDoubleClick(node.path, node.name);
457
- }
520
+ onDoubleClick: function(e) {
521
+ if (!isFolder && onFileDoubleClick) { e.stopPropagation(); onFileDoubleClick(node.path, node.name); }
458
522
  },
459
- onMouseEnter: function () {
523
+ onMouseEnter: function() {
460
524
  if (isFolder) return;
461
525
  clearTimeout(hoverTimerRef.current);
462
- hoverTimerRef.current = setTimeout(function () {
526
+ hoverTimerRef.current = setTimeout(function() {
463
527
  hoverPathRef.current = node.path;
464
528
  FileService.prefetch(node.path);
465
529
  }, 200);
466
530
  },
467
- onMouseLeave: function () {
531
+ onMouseLeave: function() {
468
532
  if (isFolder) return;
469
533
  clearTimeout(hoverTimerRef.current);
470
534
  FileService.cancelPrefetch(hoverPathRef.current);
471
535
  hoverPathRef.current = null;
472
536
  },
473
- onContextMenu: function (e) {
537
+ onContextMenu: function(e) {
474
538
  e.preventDefault();
475
539
  e.stopPropagation();
476
540
  selectNode(node);
@@ -478,9 +542,10 @@ var FileTree = function FileTree(_ref) {
478
542
  }
479
543
  },
480
544
  React.createElement(
481
- 'div',
482
- { className: 'tree-item-icon' },
483
- isFolder ? React.createElement('i', { className: 'fas fa-folder' + (isExpanded ? "-open" : "") + ' tree-folder-icon' }) : React.createElement('i', { className: window.getFileIcon(node.name) + ' tree-file-icon' })
545
+ 'div', { className: 'tree-item-icon' },
546
+ isFolder
547
+ ? React.createElement('i', { className: 'fas fa-folder' + (isExpanded ? '-open' : '') + ' tree-folder-icon' })
548
+ : React.createElement('i', { className: window.getFileIcon(node.name) + ' tree-file-icon' })
484
549
  ),
485
550
  React.createElement(
486
551
  'div',
@@ -492,27 +557,23 @@ var FileTree = function FileTree(_ref) {
492
557
  { className: 'git-status-badge git-' + statusMeta.cssKey, title: statusMeta.title },
493
558
  statusMeta.badge
494
559
  )
495
- ),
496
- isFolder && isExpanded && node.children && React.createElement(
497
- 'div',
498
- { style: { paddingLeft: "12px" } },
499
- renderTree(node.children, node.path)
500
560
  )
501
- );
502
- });
503
-
504
- // Inject inline create row at the end of this directory's list
505
- if (pendingCreate && pendingCreate.parentPath === folderPath) {
506
- rows.push(renderInlineRow());
507
- }
508
-
509
- return rows;
561
+ );
510
562
  };
511
563
 
564
+ var visibleRows = [];
565
+ for (var vi = startIdx; vi <= endIdx; vi++) {
566
+ visibleRows.push(renderRow(flatItems[vi], vi));
567
+ }
568
+
512
569
  return React.createElement(
513
570
  'div',
514
- { className: 'file-tree-root', ref: containerRef, tabIndex: 0, style: { outline: 'none' } },
515
- renderTree(items, '')
571
+ { className: 'file-tree file-tree-root', ref: containerRef, tabIndex: 0, style: { outline: 'none', padding: 0 } },
572
+ React.createElement(
573
+ 'div',
574
+ { style: { height: totalHeight, position: 'relative' } },
575
+ visibleRows
576
+ )
516
577
  );
517
578
  };
518
579
 
@@ -527,7 +588,7 @@ var FileTreeMemo = React.memo(FileTree, function(prev, next) {
527
588
  prev.activePath === next.activePath &&
528
589
  prev.selectedPaths === next.selectedPaths &&
529
590
  prev.anchorPath === next.anchorPath &&
530
- JSON.stringify(prev.gitFiles) === JSON.stringify(next.gitFiles) &&
591
+ prev.gitFiles === next.gitFiles &&
531
592
  prev.expandedDirs === next.expandedDirs &&
532
593
  prev.pendingCreate === next.pendingCreate &&
533
594
  prev.pendingRename === next.pendingRename;