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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +7 -0
- data/app/assets/javascripts/mbeditor/application.js +3 -0
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +145 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +359 -31
- data/app/assets/javascripts/mbeditor/components/FileTree.js +177 -116
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +952 -143
- data/app/assets/javascripts/mbeditor/components/TabBar.js +9 -0
- data/app/assets/javascripts/mbeditor/conflict_parser.js +48 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +420 -67
- data/app/assets/javascripts/mbeditor/editor_store.js +1 -0
- data/app/assets/javascripts/mbeditor/file_service.js +34 -6
- data/app/assets/javascripts/mbeditor/git_service.js +2 -1
- data/app/assets/javascripts/mbeditor/history_service.js +177 -0
- data/app/assets/javascripts/mbeditor/search_service.js +1 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +8 -5
- data/app/assets/stylesheets/mbeditor/application.css +112 -0
- data/app/assets/stylesheets/mbeditor/editor.css +443 -78
- data/app/channels/mbeditor/editor_channel.rb +5 -41
- data/app/controllers/mbeditor/application_controller.rb +8 -1
- data/app/controllers/mbeditor/editors_controller.rb +276 -654
- data/app/controllers/mbeditor/git_controller.rb +2 -61
- data/app/services/mbeditor/availability_probe.rb +83 -0
- data/app/services/mbeditor/code_search_service.rb +42 -0
- data/app/services/mbeditor/editor_state_service.rb +91 -0
- data/app/services/mbeditor/exclusion_matcher.rb +23 -0
- data/app/services/mbeditor/file_operation_service.rb +68 -0
- data/app/services/mbeditor/file_tree_service.rb +69 -0
- data/app/services/mbeditor/git_combined_diff_service.rb +43 -0
- data/app/services/mbeditor/git_commit_detail_service.rb +46 -0
- data/app/services/mbeditor/git_info_service.rb +151 -0
- data/app/services/mbeditor/git_service.rb +36 -26
- data/app/services/mbeditor/js_definition_service.rb +59 -0
- data/app/services/mbeditor/js_members_service.rb +62 -0
- data/app/services/mbeditor/process_runner.rb +48 -0
- data/app/services/mbeditor/rails_related_files_service.rb +282 -0
- data/app/services/mbeditor/ruby_definition_service.rb +77 -101
- data/app/services/mbeditor/schema_service.rb +270 -0
- data/app/services/mbeditor/search_replace_service.rb +184 -0
- data/app/services/mbeditor/test_runner_service.rb +5 -27
- data/app/views/layouts/mbeditor/application.html.erb +2 -2
- data/config/routes.rb +8 -1
- data/lib/mbeditor/configuration.rb +4 -2
- data/lib/mbeditor/version.rb +1 -1
- data/public/monaco-editor/vs/language/css/cssMode.js +13 -0
- data/public/monaco-editor/vs/language/css/cssWorker.js +77 -0
- data/public/monaco-editor/vs/language/html/htmlMode.js +13 -0
- data/public/monaco-editor/vs/language/html/htmlWorker.js +454 -0
- data/public/monaco-editor/vs/language/json/jsonMode.js +19 -0
- data/public/monaco-editor/vs/language/json/jsonWorker.js +42 -0
- metadata +26 -3
- data/app/services/mbeditor/unused_methods_service.rb +0 -139
|
@@ -36,9 +36,17 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
36
36
|
var monacoRef = useRef(null);
|
|
37
37
|
var latestContentRef = useRef('');
|
|
38
38
|
var lastAppliedExternalVersionRef = useRef(0);
|
|
39
|
+
var conflictDecorationsRef = React.useRef([]);
|
|
40
|
+
var conflictBlocksRef = React.useRef([]);
|
|
39
41
|
var aviBaseRef = useRef(0);
|
|
40
42
|
var aviMaxRef = useRef(0);
|
|
41
43
|
|
|
44
|
+
var _conflictState = React.useState(0);
|
|
45
|
+
var conflictCount = _conflictState[0];
|
|
46
|
+
var setConflictCount = _conflictState[1];
|
|
47
|
+
|
|
48
|
+
var currentConflictIndexRef = React.useRef(0);
|
|
49
|
+
|
|
42
50
|
var _useState = useState('');
|
|
43
51
|
var _useState2 = _slicedToArray(_useState, 2);
|
|
44
52
|
var markup = _useState2[0];
|
|
@@ -464,7 +472,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
464
472
|
language = 'ruby';break;
|
|
465
473
|
default:
|
|
466
474
|
switch (extension) {
|
|
467
|
-
case 'rb':case 'ruby':case 'gemspec':
|
|
475
|
+
case 'rb':case 'ruby':case 'gemspec':case 'rake':
|
|
468
476
|
language = 'ruby';break;
|
|
469
477
|
case 'js':case 'jsx':
|
|
470
478
|
language = 'javascript';break;
|
|
@@ -516,21 +524,22 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
516
524
|
// Evict the LRU model if the cache is at capacity before creating a new one.
|
|
517
525
|
TabManager.evictLruModel();
|
|
518
526
|
|
|
519
|
-
|
|
520
|
-
var contentForModel = tab.content;
|
|
521
|
-
if (language === 'json' && contentForModel) {
|
|
522
|
-
try {
|
|
523
|
-
contentForModel = JSON.stringify(JSON.parse(contentForModel), null, 2);
|
|
524
|
-
} catch (_) {
|
|
525
|
-
// invalid JSON — use raw content; Monaco will show error markers
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
modelObj = window.monaco.editor.createModel(contentForModel, language);
|
|
527
|
+
modelObj = window.monaco.editor.createModel(tab.content, language);
|
|
530
528
|
window.__mbeditorModels[tab.path] = { model: modelObj, aviBase: null, aviMax: null, lastAccessed: Date.now(), cleanVersionId: null };
|
|
531
529
|
_modelEntry = window.__mbeditorModels[tab.path];
|
|
532
530
|
}
|
|
533
531
|
|
|
532
|
+
if (typeof HistoryService !== 'undefined') {
|
|
533
|
+
var _histBranch = EditorStore.getState().gitBranch || '';
|
|
534
|
+
if (_histBranch) {
|
|
535
|
+
if (_reusingModel) {
|
|
536
|
+
HistoryService.resumeTracking(_histBranch, tab.path);
|
|
537
|
+
} else {
|
|
538
|
+
HistoryService.beginTracking(_histBranch, tab.path, tab.content || '');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
534
543
|
// Sync latestContentRef from the actual model content so the onDidChangeContent
|
|
535
544
|
// handler doesn't fire a spurious onContentChange call on the first keystroke.
|
|
536
545
|
latestContentRef.current = _reusingModel ? modelObj.getValue() : (tab.content || '');
|
|
@@ -563,9 +572,9 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
563
572
|
formatOnPaste: editorPrefs.formatOnPaste !== false,
|
|
564
573
|
formatOnType: editorPrefs.formatOnType !== false,
|
|
565
574
|
quickSuggestions: editorPrefs.quickSuggestions !== false,
|
|
566
|
-
wordBasedSuggestions: editorPrefs.wordBasedSuggestions || '
|
|
575
|
+
wordBasedSuggestions: editorPrefs.wordBasedSuggestions || 'currentDocument',
|
|
567
576
|
acceptSuggestionOnEnter: editorPrefs.acceptSuggestionOnEnter || 'on',
|
|
568
|
-
linkedEditing:
|
|
577
|
+
linkedEditing: !!(editorPrefs.linkedEditing),
|
|
569
578
|
fixedOverflowWidgets: true,
|
|
570
579
|
hover: { above: false }
|
|
571
580
|
});
|
|
@@ -698,6 +707,9 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
698
707
|
EditorStore.setState({ canUndo: avi > aviBaseRef.current, canRedo: avi < aviMaxRef.current });
|
|
699
708
|
|
|
700
709
|
var contentDisposable = modelObj.onDidChangeContent(function (e) {
|
|
710
|
+
if (typeof HistoryService !== 'undefined') {
|
|
711
|
+
HistoryService.recordOps(tab.path, e.changes);
|
|
712
|
+
}
|
|
701
713
|
var currentAvi = modelObj.getAlternativeVersionId();
|
|
702
714
|
if (!e.isUndoing && !e.isRedoing) {
|
|
703
715
|
// New edit: redo stack discarded at this point, so max resets here
|
|
@@ -705,7 +717,12 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
705
717
|
} else if (currentAvi > aviMaxRef.current) {
|
|
706
718
|
aviMaxRef.current = currentAvi;
|
|
707
719
|
}
|
|
708
|
-
|
|
720
|
+
var newCanUndo = currentAvi > aviBaseRef.current;
|
|
721
|
+
var newCanRedo = currentAvi < aviMaxRef.current;
|
|
722
|
+
var _st = EditorStore.getState();
|
|
723
|
+
if (_st.canUndo !== newCanUndo || _st.canRedo !== newCanRedo) {
|
|
724
|
+
EditorStore.setState({ canUndo: newCanUndo, canRedo: newCanRedo });
|
|
725
|
+
}
|
|
709
726
|
|
|
710
727
|
var val = editor.getValue();
|
|
711
728
|
|
|
@@ -735,6 +752,128 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
735
752
|
}
|
|
736
753
|
});
|
|
737
754
|
|
|
755
|
+
// Phase 2: background undo-history replay.
|
|
756
|
+
// Only run for newly-created models (reused models already have their undo stack).
|
|
757
|
+
var _phase2CleanupFn = null;
|
|
758
|
+
if (!_reusingModel && typeof HistoryService !== 'undefined') {
|
|
759
|
+
var _phase2Branch = EditorStore.getState().gitBranch || '';
|
|
760
|
+
var _phase2Path = tab.path;
|
|
761
|
+
var _phase2Content = tab.content || '';
|
|
762
|
+
var _phase2Buf = [];
|
|
763
|
+
var _phase2Active = true;
|
|
764
|
+
|
|
765
|
+
var _phase2ModelA = modelObj;
|
|
766
|
+
var _phase2Listener = _phase2ModelA.onDidChangeContent(function (ev) {
|
|
767
|
+
if (!_phase2Active) return;
|
|
768
|
+
for (var _ci = 0; _ci < ev.changes.length; _ci++) {
|
|
769
|
+
var _c = ev.changes[_ci];
|
|
770
|
+
_phase2Buf.push([
|
|
771
|
+
_c.range.startLineNumber, _c.range.startColumn,
|
|
772
|
+
_c.range.endLineNumber, _c.range.endColumn,
|
|
773
|
+
_c.text
|
|
774
|
+
]);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
var _runPhase2 = function () {
|
|
779
|
+
if (!_phase2Active || !_phase2Branch) return;
|
|
780
|
+
HistoryService.fetchHistory(_phase2Branch, _phase2Path).then(function (hist) {
|
|
781
|
+
if (!_phase2Active) return;
|
|
782
|
+
if (!hist || !hist.ops || hist.ops.length === 0) {
|
|
783
|
+
_phase2Listener.dispose();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
var _lang = modelObj.getLanguageId();
|
|
788
|
+
var modelB = window.monaco.editor.createModel(hist.base, _lang);
|
|
789
|
+
|
|
790
|
+
HistoryService.setReplayInProgress(_phase2Path, true);
|
|
791
|
+
try {
|
|
792
|
+
for (var _oi = 0; _oi < hist.ops.length; _oi++) {
|
|
793
|
+
var _op = hist.ops[_oi];
|
|
794
|
+
modelB.pushEditOperations([], [{
|
|
795
|
+
range: new window.monaco.Range(_op[0], _op[1], _op[2], _op[3]),
|
|
796
|
+
text: _op[4] || ''
|
|
797
|
+
}], function () { return null; });
|
|
798
|
+
}
|
|
799
|
+
} catch (e) {
|
|
800
|
+
HistoryService.setReplayInProgress(_phase2Path, false);
|
|
801
|
+
_phase2Listener.dispose();
|
|
802
|
+
modelB.dispose();
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
HistoryService.setReplayInProgress(_phase2Path, false);
|
|
806
|
+
|
|
807
|
+
var _expectedContent = _phase2ModelA.getValue();
|
|
808
|
+
if (modelB.getValue() !== _expectedContent) {
|
|
809
|
+
_phase2Listener.dispose();
|
|
810
|
+
modelB.dispose();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (_phase2Buf.length > 0) {
|
|
815
|
+
try {
|
|
816
|
+
for (var _bi = 0; _bi < _phase2Buf.length; _bi++) {
|
|
817
|
+
var _bop = _phase2Buf[_bi];
|
|
818
|
+
modelB.pushEditOperations([], [{
|
|
819
|
+
range: new window.monaco.Range(_bop[0], _bop[1], _bop[2], _bop[3]),
|
|
820
|
+
text: _bop[4] || ''
|
|
821
|
+
}], function () { return null; });
|
|
822
|
+
}
|
|
823
|
+
} catch (e) {
|
|
824
|
+
_phase2Listener.dispose();
|
|
825
|
+
modelB.dispose();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
_phase2Listener.dispose();
|
|
831
|
+
if (!_phase2Active) { modelB.dispose(); return; }
|
|
832
|
+
|
|
833
|
+
if (modelB.getLanguageId() !== _lang) {
|
|
834
|
+
window.monaco.editor.setModelLanguage(modelB, _lang);
|
|
835
|
+
}
|
|
836
|
+
modelB._mbeditorPath = _phase2Path;
|
|
837
|
+
|
|
838
|
+
var _vs = editor.saveViewState();
|
|
839
|
+
editor.setModel(modelB);
|
|
840
|
+
if (_vs) editor.restoreViewState(_vs);
|
|
841
|
+
|
|
842
|
+
var _oldEntry = window.__mbeditorModels[_phase2Path];
|
|
843
|
+
if (_oldEntry && _oldEntry.model !== modelB) {
|
|
844
|
+
var _oldModel = _oldEntry.model;
|
|
845
|
+
window.__mbeditorModels[_phase2Path] = {
|
|
846
|
+
model: modelB,
|
|
847
|
+
aviBase: aviBaseRef.current,
|
|
848
|
+
aviMax: modelB.getAlternativeVersionId(),
|
|
849
|
+
lastAccessed: Date.now(),
|
|
850
|
+
cleanVersionId: _oldEntry.cleanVersionId
|
|
851
|
+
};
|
|
852
|
+
setTimeout(function () {
|
|
853
|
+
if (_oldModel && !_oldModel.isDisposed()) _oldModel.dispose();
|
|
854
|
+
}, 0);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Re-attach the HistoryService content listener to modelB so ops recorded
|
|
858
|
+
// after the swap are captured without waiting for the next tab switch.
|
|
859
|
+
var _modelBListener = modelB.onDidChangeContent(function (ev) {
|
|
860
|
+
HistoryService.recordOps(_phase2Path, ev.changes);
|
|
861
|
+
});
|
|
862
|
+
_phase2CleanupFn = function () { _modelBListener.dispose(); };
|
|
863
|
+
}).catch(function () {
|
|
864
|
+
_phase2Listener.dispose();
|
|
865
|
+
});
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
869
|
+
requestIdleCallback(_runPhase2, { timeout: 2000 });
|
|
870
|
+
} else {
|
|
871
|
+
setTimeout(_runPhase2, 200);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
_phase2CleanupFn = function () { _phase2Active = false; _phase2Listener.dispose(); };
|
|
875
|
+
}
|
|
876
|
+
|
|
738
877
|
return function () {
|
|
739
878
|
blameDecorationsRef.current = editor.deltaDecorations(blameDecorationsRef.current, []);
|
|
740
879
|
testDecorationIdsRef.current = editor.deltaDecorations(testDecorationIdsRef.current, []);
|
|
@@ -775,6 +914,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
775
914
|
columnSelectDisposable.dispose();
|
|
776
915
|
contentDisposable.dispose();
|
|
777
916
|
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
917
|
+
if (_phase2CleanupFn) _phase2CleanupFn();
|
|
778
918
|
// Detach the model before disposing the editor so the model (and its undo
|
|
779
919
|
// history) survives for when the user returns to this tab.
|
|
780
920
|
editor.setModel(null);
|
|
@@ -811,18 +951,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
811
951
|
var _initEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
812
952
|
if (_initEntry) _initEntry.cleanVersionId = null;
|
|
813
953
|
|
|
814
|
-
|
|
815
|
-
var contentToSet = tab.content;
|
|
816
|
-
var modelLang = model.getLanguageId();
|
|
817
|
-
if (modelLang === 'json' && contentToSet) {
|
|
818
|
-
try {
|
|
819
|
-
contentToSet = JSON.stringify(JSON.parse(contentToSet), null, 2);
|
|
820
|
-
} catch (_) {
|
|
821
|
-
// invalid JSON — use raw content; Monaco will show error markers
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
editor.setValue(contentToSet);
|
|
954
|
+
editor.setValue(tab.content);
|
|
826
955
|
// Reset the AVI baseline: setValue clears the undo stack so anything before
|
|
827
956
|
// this point is no longer reachable. Also clear the canUndo/canRedo display.
|
|
828
957
|
var newBase = model.getAlternativeVersionId();
|
|
@@ -848,6 +977,54 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
848
977
|
}
|
|
849
978
|
}, [tab.content, tab.externalContentVersion]);
|
|
850
979
|
|
|
980
|
+
useEffect(function () {
|
|
981
|
+
var editor = monacoRef.current;
|
|
982
|
+
if (!editor || !window.monaco || typeof tab.content !== 'string') return;
|
|
983
|
+
var model = editor.getModel();
|
|
984
|
+
if (!model) return;
|
|
985
|
+
|
|
986
|
+
if (!ConflictParser.hasConflicts(tab.content)) {
|
|
987
|
+
conflictDecorationsRef.current = model.deltaDecorations(conflictDecorationsRef.current, []);
|
|
988
|
+
conflictBlocksRef.current = [];
|
|
989
|
+
setConflictCount(0);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
var blocks = ConflictParser.parse(tab.content);
|
|
994
|
+
conflictBlocksRef.current = blocks;
|
|
995
|
+
setConflictCount(blocks.length);
|
|
996
|
+
|
|
997
|
+
var decorations = [];
|
|
998
|
+
blocks.forEach(function (block) {
|
|
999
|
+
decorations.push({
|
|
1000
|
+
range: new window.monaco.Range(block.startLine + 1, 1, block.startLine + 1, 1),
|
|
1001
|
+
options: { isWholeLine: true, className: 'mb-conflict-marker-line' }
|
|
1002
|
+
});
|
|
1003
|
+
if (block.headEnd > block.startLine + 1) {
|
|
1004
|
+
decorations.push({
|
|
1005
|
+
range: new window.monaco.Range(block.startLine + 2, 1, block.headEnd, 1),
|
|
1006
|
+
options: { isWholeLine: true, className: 'mb-conflict-head' }
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
decorations.push({
|
|
1010
|
+
range: new window.monaco.Range(block.dividerLine + 1, 1, block.dividerLine + 1, 1),
|
|
1011
|
+
options: { isWholeLine: true, className: 'mb-conflict-marker-line' }
|
|
1012
|
+
});
|
|
1013
|
+
if (block.endLine > block.dividerLine + 1) {
|
|
1014
|
+
decorations.push({
|
|
1015
|
+
range: new window.monaco.Range(block.dividerLine + 2, 1, block.endLine, 1),
|
|
1016
|
+
options: { isWholeLine: true, className: 'mb-conflict-incoming' }
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
decorations.push({
|
|
1020
|
+
range: new window.monaco.Range(block.endLine + 1, 1, block.endLine + 1, 1),
|
|
1021
|
+
options: { isWholeLine: true, className: 'mb-conflict-marker-line' }
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
conflictDecorationsRef.current = model.deltaDecorations(conflictDecorationsRef.current, decorations);
|
|
1026
|
+
}, [tab.content, tab.externalContentVersion]);
|
|
1027
|
+
|
|
851
1028
|
// Apply editorPrefs changes to a running editor without remounting
|
|
852
1029
|
useEffect(function () {
|
|
853
1030
|
if (!window.monaco) return;
|
|
@@ -879,7 +1056,8 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
879
1056
|
formatOnPaste: editorPrefs.formatOnPaste !== false,
|
|
880
1057
|
formatOnType: editorPrefs.formatOnType !== false,
|
|
881
1058
|
quickSuggestions: editorPrefs.quickSuggestions !== false,
|
|
882
|
-
wordBasedSuggestions: editorPrefs.wordBasedSuggestions || '
|
|
1059
|
+
wordBasedSuggestions: editorPrefs.wordBasedSuggestions || 'currentDocument',
|
|
1060
|
+
linkedEditing: !!(editorPrefs.linkedEditing),
|
|
883
1061
|
acceptSuggestionOnEnter: editorPrefs.acceptSuggestionOnEnter || 'on'
|
|
884
1062
|
});
|
|
885
1063
|
}
|
|
@@ -914,6 +1092,20 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
914
1092
|
});
|
|
915
1093
|
MonacoVim.VimMode.Vim.map('<C-p>', ':mbeditorquickopen<CR>', 'normal');
|
|
916
1094
|
MonacoVim.VimMode.Vim.map('<C-p>', ':mbeditorquickopen<CR>', 'visual');
|
|
1095
|
+
// :split / :vsplit — open the current file in the other pane and focus it.
|
|
1096
|
+
// Both map to the same behaviour since the editor always has exactly two panes.
|
|
1097
|
+
var openInOtherPane = function() {
|
|
1098
|
+
var model = monacoRef.current && monacoRef.current.getModel();
|
|
1099
|
+
var filePath = model && model._mbeditorPath;
|
|
1100
|
+
if (!filePath || typeof TabManager === 'undefined') return;
|
|
1101
|
+
var otherPaneId = paneId === 1 ? 2 : 1;
|
|
1102
|
+
// forcePaneId (4th arg) bypasses the "redirect empty pane 2 → pane 1" guard.
|
|
1103
|
+
TabManager.openTab(filePath, filePath.split('/').pop(), null, otherPaneId);
|
|
1104
|
+
TabManager.focusPane(otherPaneId);
|
|
1105
|
+
window.dispatchEvent(new CustomEvent('mbeditor:focusPane', { detail: { paneId: otherPaneId } }));
|
|
1106
|
+
};
|
|
1107
|
+
MonacoVim.VimMode.Vim.defineEx('split', 'sp', openInOtherPane);
|
|
1108
|
+
MonacoVim.VimMode.Vim.defineEx('vsplit', 'vs', openInOtherPane);
|
|
917
1109
|
vimModeObjRef.current = vimInstance;
|
|
918
1110
|
});
|
|
919
1111
|
} else {
|
|
@@ -931,6 +1123,18 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
931
1123
|
};
|
|
932
1124
|
}, [editorPrefs.vimMode]);
|
|
933
1125
|
|
|
1126
|
+
// Focus this pane's Monaco editor when MbeditorApp dispatches mbeditor:focusPane
|
|
1127
|
+
// (used by the vim Ctrl+W panel-switching handler to move keyboard focus).
|
|
1128
|
+
useEffect(function() {
|
|
1129
|
+
function onFocusPane(e) {
|
|
1130
|
+
if (e.detail && e.detail.paneId === paneId && monacoRef.current) {
|
|
1131
|
+
monacoRef.current.focus();
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
window.addEventListener('mbeditor:focusPane', onFocusPane);
|
|
1135
|
+
return function() { window.removeEventListener('mbeditor:focusPane', onFocusPane); };
|
|
1136
|
+
}, [paneId]);
|
|
1137
|
+
|
|
934
1138
|
// Jump to line if specified
|
|
935
1139
|
useEffect(function () {
|
|
936
1140
|
if (tab.gotoLine && monacoRef.current) {
|
|
@@ -938,7 +1142,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
938
1142
|
var editor = monacoRef.current;
|
|
939
1143
|
setTimeout(function () {
|
|
940
1144
|
editor.revealLineInCenter(tab.gotoLine);
|
|
941
|
-
editor.setPosition({ lineNumber: tab.gotoLine, column: 1 });
|
|
1145
|
+
editor.setPosition({ lineNumber: tab.gotoLine, column: tab.gotoCol || 1 });
|
|
942
1146
|
editor.focus();
|
|
943
1147
|
|
|
944
1148
|
TabManager.saveTabViewState(tab.id, editor.saveViewState());
|
|
@@ -1335,7 +1539,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1335
1539
|
var isImage = tab.isImage || IMAGE_EXTENSIONS.includes(ext);
|
|
1336
1540
|
var isMarkdown = ['md', 'markdown'].includes(ext);
|
|
1337
1541
|
var fileBaseName = (tab.path || '').split('/').pop().toLowerCase();
|
|
1338
|
-
var isRubyFile = ext === 'rb' || ext === 'ruby' || ext === 'gemspec' ||
|
|
1542
|
+
var isRubyFile = ext === 'rb' || ext === 'ruby' || ext === 'gemspec' || ext === 'rake' ||
|
|
1339
1543
|
fileBaseName === 'gemfile' || fileBaseName === 'gemfile.lock' || fileBaseName === 'rakefile';
|
|
1340
1544
|
|
|
1341
1545
|
useEffect(function () {
|
|
@@ -1458,6 +1662,57 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1458
1662
|
);
|
|
1459
1663
|
}
|
|
1460
1664
|
|
|
1665
|
+
function resolveConflict(blockIndex, resolution) {
|
|
1666
|
+
var editor = monacoRef.current;
|
|
1667
|
+
if (!editor || !window.monaco) return;
|
|
1668
|
+
var blocks = conflictBlocksRef.current;
|
|
1669
|
+
if (blockIndex < 0 || blockIndex >= blocks.length) return;
|
|
1670
|
+
var block = blocks[blockIndex];
|
|
1671
|
+
var model = editor.getModel();
|
|
1672
|
+
if (!model) return;
|
|
1673
|
+
|
|
1674
|
+
var resolvedContent;
|
|
1675
|
+
if (resolution === 'head') {
|
|
1676
|
+
resolvedContent = block.headContent;
|
|
1677
|
+
} else if (resolution === 'incoming') {
|
|
1678
|
+
resolvedContent = block.incomingContent;
|
|
1679
|
+
} else {
|
|
1680
|
+
resolvedContent = block.headContent +
|
|
1681
|
+
(block.headContent && block.incomingContent ? '\n' : '') +
|
|
1682
|
+
block.incomingContent;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
editor.pushUndoStop();
|
|
1686
|
+
editor.executeEdits('conflict-resolve', [{
|
|
1687
|
+
range: new window.monaco.Range(
|
|
1688
|
+
block.startLine + 1, 1,
|
|
1689
|
+
block.endLine + 1,
|
|
1690
|
+
model.getLineMaxColumn(block.endLine + 1)
|
|
1691
|
+
),
|
|
1692
|
+
text: resolvedContent
|
|
1693
|
+
}]);
|
|
1694
|
+
editor.pushUndoStop();
|
|
1695
|
+
|
|
1696
|
+
var newContent = editor.getValue();
|
|
1697
|
+
EditorStore.setState({
|
|
1698
|
+
panes: EditorStore.getState().panes.map(function (p) {
|
|
1699
|
+
return Object.assign({}, p, {
|
|
1700
|
+
tabs: p.tabs.map(function (t) {
|
|
1701
|
+
if (t.path !== tab.path) return t;
|
|
1702
|
+
return Object.assign({}, t, {
|
|
1703
|
+
content: newContent,
|
|
1704
|
+
dirty: true,
|
|
1705
|
+
externalContentVersion: (t.externalContentVersion || 0) + 1
|
|
1706
|
+
});
|
|
1707
|
+
})
|
|
1708
|
+
});
|
|
1709
|
+
})
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
var remaining = conflictBlocksRef.current.length - 1;
|
|
1713
|
+
currentConflictIndexRef.current = Math.min(currentConflictIndexRef.current, Math.max(0, remaining - 1));
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1461
1716
|
// Always render the same wrapper structure so the editorRef div is never
|
|
1462
1717
|
// unmounted when gitAvailable changes (e.g. loaded async after workspace
|
|
1463
1718
|
// call returns). The toolbar is conditionally included inside the wrapper.
|
|
@@ -1532,6 +1787,65 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1532
1787
|
!editorPrefs.toolbarIconOnly && !testLoading && React.createElement('span', { className: 'ide-toolbar-label' }, 'Test')
|
|
1533
1788
|
)
|
|
1534
1789
|
),
|
|
1790
|
+
conflictCount > 0 && React.createElement(
|
|
1791
|
+
'div', { className: 'mb-conflict-banner' },
|
|
1792
|
+
React.createElement(
|
|
1793
|
+
'span', { className: 'mb-conflict-count' },
|
|
1794
|
+
React.createElement('i', { className: 'fas fa-code-merge' }),
|
|
1795
|
+
' ',
|
|
1796
|
+
conflictCount,
|
|
1797
|
+
' merge conflict',
|
|
1798
|
+
conflictCount !== 1 ? 's' : ''
|
|
1799
|
+
),
|
|
1800
|
+
React.createElement(
|
|
1801
|
+
'div', { className: 'mb-conflict-nav' },
|
|
1802
|
+
React.createElement('button', {
|
|
1803
|
+
className: 'mb-btn mb-btn-sm',
|
|
1804
|
+
title: 'Previous conflict',
|
|
1805
|
+
onClick: function () {
|
|
1806
|
+
var idx = Math.max(0, currentConflictIndexRef.current - 1);
|
|
1807
|
+
currentConflictIndexRef.current = idx;
|
|
1808
|
+
var b = conflictBlocksRef.current[idx];
|
|
1809
|
+
if (b && monacoRef.current) {
|
|
1810
|
+
monacoRef.current.revealLineInCenter(b.startLine + 1);
|
|
1811
|
+
monacoRef.current.setPosition({ lineNumber: b.startLine + 1, column: 1 });
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}, '↑ Prev'),
|
|
1815
|
+
React.createElement('button', {
|
|
1816
|
+
className: 'mb-btn mb-btn-sm',
|
|
1817
|
+
title: 'Next conflict',
|
|
1818
|
+
onClick: function () {
|
|
1819
|
+
var blocks = conflictBlocksRef.current;
|
|
1820
|
+
var idx = Math.min(blocks.length - 1, currentConflictIndexRef.current + 1);
|
|
1821
|
+
currentConflictIndexRef.current = idx;
|
|
1822
|
+
var b = blocks[idx];
|
|
1823
|
+
if (b && monacoRef.current) {
|
|
1824
|
+
monacoRef.current.revealLineInCenter(b.startLine + 1);
|
|
1825
|
+
monacoRef.current.setPosition({ lineNumber: b.startLine + 1, column: 1 });
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}, '↓ Next')
|
|
1829
|
+
),
|
|
1830
|
+
React.createElement(
|
|
1831
|
+
'div', { className: 'mb-conflict-actions' },
|
|
1832
|
+
React.createElement('button', {
|
|
1833
|
+
className: 'mb-btn mb-btn-sm mb-btn-success',
|
|
1834
|
+
title: 'Accept current (HEAD) — keep your local changes',
|
|
1835
|
+
onClick: function () { resolveConflict(currentConflictIndexRef.current, 'head'); }
|
|
1836
|
+
}, 'Accept Current'),
|
|
1837
|
+
React.createElement('button', {
|
|
1838
|
+
className: 'mb-btn mb-btn-sm mb-btn-incoming',
|
|
1839
|
+
title: 'Accept incoming — take the changes being merged in',
|
|
1840
|
+
onClick: function () { resolveConflict(currentConflictIndexRef.current, 'incoming'); }
|
|
1841
|
+
}, 'Accept Incoming'),
|
|
1842
|
+
React.createElement('button', {
|
|
1843
|
+
className: 'mb-btn mb-btn-sm',
|
|
1844
|
+
title: 'Accept both — keep current above incoming',
|
|
1845
|
+
onClick: function () { resolveConflict(currentConflictIndexRef.current, 'both'); }
|
|
1846
|
+
}, 'Accept Both')
|
|
1847
|
+
)
|
|
1848
|
+
),
|
|
1535
1849
|
tab.truncated && React.createElement(
|
|
1536
1850
|
'div',
|
|
1537
1851
|
{
|
|
@@ -1665,4 +1979,18 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1665
1979
|
);
|
|
1666
1980
|
};
|
|
1667
1981
|
|
|
1668
|
-
|
|
1982
|
+
// treeData and all function props (onSave, onFormat, etc.) are intentionally
|
|
1983
|
+
// excluded — their references change every parent render but don't affect editor output.
|
|
1984
|
+
window.EditorPanel = React.memo(EditorPanel, function(prev, next) {
|
|
1985
|
+
return prev.tab === next.tab &&
|
|
1986
|
+
prev.paneId === next.paneId &&
|
|
1987
|
+
(prev.markers === next.markers || (prev.markers.length === 0 && next.markers.length === 0)) &&
|
|
1988
|
+
prev.gitAvailable === next.gitAvailable &&
|
|
1989
|
+
prev.testAvailable === next.testAvailable &&
|
|
1990
|
+
prev.testResult === next.testResult &&
|
|
1991
|
+
prev.testPanelFile === next.testPanelFile &&
|
|
1992
|
+
prev.testLoading === next.testLoading &&
|
|
1993
|
+
prev.testInlineVisible === next.testInlineVisible &&
|
|
1994
|
+
prev.editorPrefs === next.editorPrefs &&
|
|
1995
|
+
prev.monacoReady === next.monacoReady;
|
|
1996
|
+
});
|