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
@@ -15,6 +15,7 @@ var EditorStore = (function () {
15
15
  searchResults: [],
16
16
  searchCapped: false,
17
17
  statusMessage: { text: "", kind: "info" },
18
+ pendingReloads: [],
18
19
  canUndo: false,
19
20
  canRedo: false,
20
21
  };
@@ -95,7 +95,7 @@ var FileService = (function () {
95
95
  }
96
96
 
97
97
  function runTests(path) {
98
- return axios.post(window.mbeditorBasePath() + '/test', { path: path }).then(function(res) { return res.data; });
98
+ return axios.post(window.mbeditorBasePath() + '/test', { path: path }, { timeout: 120000 }).then(function(res) { return res.data; });
99
99
  }
100
100
 
101
101
  function ping() {
@@ -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
 
@@ -189,6 +188,16 @@ var FileService = (function () {
189
188
  prefetchCache.delete(path);
190
189
  }
191
190
 
191
+ function getJsDefinition(symbol, extraOptions) {
192
+ var config = Object.assign({ params: { symbol: symbol }, timeout: 5000 }, extraOptions || {});
193
+ return axios.get(window.mbeditorBasePath() + '/js_definition', config).then(function(res) { return res.data; });
194
+ }
195
+
196
+ function getJsMembers(symbol, extraOptions) {
197
+ var config = Object.assign({ params: { symbol: symbol }, timeout: 5000 }, extraOptions || {});
198
+ return axios.get(window.mbeditorBasePath() + '/js_members', config).then(function(res) { return res.data; });
199
+ }
200
+
192
201
  function getModuleMembers(name, extraOptions) {
193
202
  var config = Object.assign({ params: { name: name }, timeout: 8000 }, extraOptions || {});
194
203
  return axios.get(window.mbeditorBasePath() + '/module_members', config).then(function(res) { return res.data; });
@@ -199,9 +208,23 @@ var FileService = (function () {
199
208
  return axios.get(window.mbeditorBasePath() + '/file_includes', config).then(function(res) { return res.data; });
200
209
  }
201
210
 
202
- function getUnusedMethods(path, extraOptions) {
203
- var config = Object.assign({ params: { path: path }, timeout: 30000 }, extraOptions || {});
204
- return axios.get(window.mbeditorBasePath() + '/unused_methods', config).then(function(res) { return res.data; });
211
+ function getClientConfig() {
212
+ return axios.get(window.mbeditorBasePath() + '/client_config').then(function(res) { return res.data; });
213
+ }
214
+
215
+ function getRelatedFiles(path) {
216
+ return axios.get(window.mbeditorBasePath() + '/related_files', { params: { path: path } })
217
+ .then(function(res) { return res.data; });
218
+ }
219
+
220
+ function getChangelog() {
221
+ return axios.get(window.mbeditorBasePath() + '/changelog')
222
+ .then(function(res) { return res.data; });
223
+ }
224
+
225
+ function getModelSchema(model) {
226
+ return axios.get(window.mbeditorBasePath() + '/model_schema', { params: { model: model } })
227
+ .then(function(res) { return res.data; });
205
228
  }
206
229
 
207
230
  return {
@@ -225,11 +248,16 @@ var FileService = (function () {
225
248
  saveBranchState: saveBranchState,
226
249
  pruneBranchStates: pruneBranchStates,
227
250
  getDefinition: getDefinition,
251
+ getJsDefinition: getJsDefinition,
252
+ getJsMembers: getJsMembers,
228
253
  prefetch: prefetch,
229
254
  getPrefetched: getPrefetched,
230
255
  cancelPrefetch: cancelPrefetch,
231
256
  getModuleMembers: getModuleMembers,
232
257
  getFileIncludes: getFileIncludes,
233
- getUnusedMethods: getUnusedMethods
258
+ getClientConfig: getClientConfig,
259
+ getRelatedFiles: getRelatedFiles,
260
+ getModelSchema: getModelSchema,
261
+ getChangelog: getChangelog
234
262
  };
235
263
  })();
@@ -7,7 +7,8 @@ var GitService = (function () {
7
7
  gitInfo: data,
8
8
  gitInfoError: null
9
9
  };
10
- if (JSON.stringify(files) !== JSON.stringify(current)) {
10
+ var gitSig = function(arr) { return arr.map(function(f) { return f.path + '\x00' + f.status; }).join('\x01'); };
11
+ if (gitSig(files) !== gitSig(current)) {
11
12
  stateUpdate.gitFiles = files;
12
13
  }
13
14
  EditorStore.setState(stateUpdate);
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ var HistoryService = (function () {
4
+ // _tracking[filePath] = { branch }
5
+ var _tracking = {};
6
+ // _pending["branch:filePath"] = [[sl,sc,el,ec,text], ...]
7
+ var _pending = {};
8
+ // _bases["branch:filePath"] = "original content" — cleared after first flush
9
+ var _bases = {};
10
+ // _replayingPaths: Set of filePaths currently undergoing Phase 2 replay
11
+ var _replayingPaths = {};
12
+ // _idleTimers["branch:filePath"] = timerHandle
13
+ var _idleTimers = {};
14
+
15
+ var IDLE_MS = 30000;
16
+
17
+ function _key(branch, filePath) {
18
+ return branch + ':' + filePath;
19
+ }
20
+
21
+ function beginTracking(branch, filePath, baseContent) {
22
+ _tracking[filePath] = { branch: branch };
23
+ var k = _key(branch, filePath);
24
+ _pending[k] = _pending[k] || [];
25
+ _bases[k] = baseContent;
26
+ }
27
+
28
+ function resumeTracking(branch, filePath) {
29
+ _tracking[filePath] = { branch: branch };
30
+ var k = _key(branch, filePath);
31
+ _pending[k] = _pending[k] || [];
32
+ }
33
+
34
+ function stopTracking(filePath) {
35
+ var rec = _tracking[filePath];
36
+ if (!rec) return;
37
+ var k = _key(rec.branch, filePath);
38
+ clearTimeout(_idleTimers[k]);
39
+ delete _idleTimers[k];
40
+ flush(rec.branch, filePath);
41
+ delete _tracking[filePath];
42
+ }
43
+
44
+ function setReplayInProgress(filePath, inProgress) {
45
+ if (inProgress) {
46
+ _replayingPaths[filePath] = true;
47
+ } else {
48
+ delete _replayingPaths[filePath];
49
+ }
50
+ }
51
+
52
+ function recordOps(filePath, changes) {
53
+ if (_replayingPaths[filePath]) return;
54
+ var rec = _tracking[filePath];
55
+ if (!rec) return;
56
+ var k = _key(rec.branch, filePath);
57
+ var bucket = _pending[k] = _pending[k] || [];
58
+ for (var i = 0; i < changes.length; i++) {
59
+ var c = changes[i];
60
+ bucket.push([
61
+ c.range.startLineNumber,
62
+ c.range.startColumn,
63
+ c.range.endLineNumber,
64
+ c.range.endColumn,
65
+ c.text
66
+ ]);
67
+ }
68
+ clearTimeout(_idleTimers[k]);
69
+ _idleTimers[k] = setTimeout(function () { flush(rec.branch, filePath); }, IDLE_MS);
70
+ }
71
+
72
+ function flush(branch, filePath) {
73
+ var k = _key(branch, filePath);
74
+ var ops = _pending[k];
75
+ if (!ops || ops.length === 0) return;
76
+ _pending[k] = [];
77
+ clearTimeout(_idleTimers[k]);
78
+ delete _idleTimers[k];
79
+
80
+ var body = { branch: branch, path: filePath, ops: ops };
81
+ if (_bases.hasOwnProperty(k)) {
82
+ body.base = _bases[k];
83
+ delete _bases[k];
84
+ }
85
+
86
+ try {
87
+ fetch(window.mbeditorBasePath() + '/file_history', {
88
+ method: 'POST',
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ 'X-Mbeditor-Client': '1'
92
+ },
93
+ body: JSON.stringify(body)
94
+ }).catch(function () {
95
+ // On failure, put ops back so next flush retries
96
+ var existing = _pending[k] || [];
97
+ _pending[k] = ops.concat(existing);
98
+ if (body.base !== undefined && !_bases.hasOwnProperty(k)) {
99
+ _bases[k] = body.base;
100
+ }
101
+ });
102
+ } catch (e) {}
103
+ }
104
+
105
+ function flushAll(options) {
106
+ var useKeepalive = options && options.keepalive;
107
+ Object.keys(_tracking).forEach(function (filePath) {
108
+ var rec = _tracking[filePath];
109
+ var k = _key(rec.branch, filePath);
110
+ var ops = _pending[k];
111
+ if (!ops || ops.length === 0) return;
112
+ var remaining = ops.slice();
113
+ _pending[k] = [];
114
+ clearTimeout(_idleTimers[k]);
115
+ delete _idleTimers[k];
116
+
117
+ var body = { branch: rec.branch, path: filePath, ops: remaining };
118
+ if (_bases.hasOwnProperty(k)) {
119
+ body.base = _bases[k];
120
+ delete _bases[k];
121
+ }
122
+
123
+ try {
124
+ fetch(window.mbeditorBasePath() + '/file_history', {
125
+ method: 'POST',
126
+ headers: {
127
+ 'Content-Type': 'application/json',
128
+ 'X-Mbeditor-Client': '1'
129
+ },
130
+ keepalive: useKeepalive === true,
131
+ body: JSON.stringify(body)
132
+ });
133
+ } catch (e) {}
134
+ });
135
+ }
136
+
137
+ function fetchHistory(branch, filePath) {
138
+ return fetch(
139
+ window.mbeditorBasePath() + '/file_history' +
140
+ '?branch=' + encodeURIComponent(branch) +
141
+ '&path=' + encodeURIComponent(filePath),
142
+ { headers: { 'X-Mbeditor-Client': '1' } }
143
+ ).then(function (res) {
144
+ if (!res.ok) return null;
145
+ return res.json();
146
+ }).then(function (data) {
147
+ if (!data || !data.ops || data.ops.length === 0) return null;
148
+ return data;
149
+ }).catch(function () { return null; });
150
+ }
151
+
152
+ // Global flush triggers
153
+ document.addEventListener('visibilitychange', function () {
154
+ if (document.visibilityState === 'hidden') flushAll({});
155
+ });
156
+
157
+ window.addEventListener('beforeunload', function () {
158
+ flushAll({ keepalive: true });
159
+ });
160
+
161
+ function flushForPath(filePath) {
162
+ var rec = _tracking[filePath];
163
+ if (rec) flush(rec.branch, filePath);
164
+ }
165
+
166
+ return {
167
+ beginTracking: beginTracking,
168
+ resumeTracking: resumeTracking,
169
+ stopTracking: stopTracking,
170
+ setReplayInProgress: setReplayInProgress,
171
+ recordOps: recordOps,
172
+ flush: flush,
173
+ flushAll: flushAll,
174
+ flushForPath: flushForPath,
175
+ fetchHistory: fetchHistory
176
+ };
177
+ })();
@@ -61,6 +61,7 @@ var SearchService = (function () {
61
61
 
62
62
  function traverse(nodes) {
63
63
  nodes.forEach(function(n) {
64
+ if (n.excluded) return; // skip excluded paths and their descendants
64
65
  if (n.type === 'file') {
65
66
  docs.push({ id: idCounter++, path: n.path, name: n.name, type: 'file' });
66
67
  } else if (n.type === 'folder') {
@@ -1,5 +1,5 @@
1
1
  var TabManager = (function () {
2
- var MAX_MODELS = 15;
2
+ var MAX_MODELS = 25;
3
3
 
4
4
  // Evict the least-recently-used Monaco model that is not currently open in
5
5
  // any pane. Call this before creating a new model entry.
@@ -118,7 +118,7 @@ var TabManager = (function () {
118
118
  });
119
119
  }
120
120
 
121
- function openTab(path, name, line, forcePaneId, isSoftOpen) {
121
+ function openTab(path, name, line, forcePaneId, isSoftOpen, col) {
122
122
  var state = EditorStore.getState();
123
123
  var paneId = forcePaneId || state.focusedPaneId;
124
124
  var pane = state.panes.find(function(p) { return p.id === paneId; });
@@ -137,7 +137,7 @@ var TabManager = (function () {
137
137
  var existing = pane.tabs.find(function(t) { return t.path === path; });
138
138
 
139
139
  if (existing) {
140
- if (line) _updateTab(paneId, path, { gotoLine: line });
140
+ if (line) _updateTab(paneId, path, { gotoLine: line, gotoCol: col || null });
141
141
  switchTab(paneId, path);
142
142
  if (_isMarkdownPath(path)) {
143
143
  _ensureMarkdownPreview(paneId, path, existing.name || name, existing.content || "");
@@ -163,7 +163,7 @@ var TabManager = (function () {
163
163
  isSoftOpen: isSoftOpen ? true : false,
164
164
  loading: true
165
165
  };
166
- if (line) newTab.gotoLine = line;
166
+ if (line) { newTab.gotoLine = line; newTab.gotoCol = col || null; }
167
167
 
168
168
  var newPanes = state.panes.map(function(p) {
169
169
  if (p.id === paneId) {
@@ -349,6 +349,9 @@ var TabManager = (function () {
349
349
  }
350
350
 
351
351
  function closeTab(paneId, path) {
352
+ if (typeof HistoryService !== 'undefined') {
353
+ HistoryService.flushForPath(path);
354
+ }
352
355
  var state = EditorStore.getState();
353
356
  var newPanes = state.panes.map(function(pane) {
354
357
  if (pane.id === paneId) {
@@ -553,7 +556,7 @@ var TabManager = (function () {
553
556
  }
554
557
 
555
558
  function clearGotoLine(paneId, path) {
556
- _updateTab(paneId, path, { gotoLine: null });
559
+ _updateTab(paneId, path, { gotoLine: null, gotoCol: null });
557
560
  }
558
561
 
559
562
  return {
@@ -861,3 +861,115 @@
861
861
  }
862
862
 
863
863
  .cdiff-ctx { color: var(--ide-text-muted); }
864
+
865
+ /* ── File Reload Banner ─────────────────────────────────────────── */
866
+ .mb-file-reload-banner {
867
+ display: flex;
868
+ flex-direction: column;
869
+ gap: 0;
870
+ flex-shrink: 0;
871
+ }
872
+
873
+ .mb-file-reload-item {
874
+ display: flex;
875
+ align-items: center;
876
+ justify-content: space-between;
877
+ padding: 5px 12px;
878
+ background: #2a2600;
879
+ border-bottom: 1px solid #5a5000;
880
+ font-size: 12px;
881
+ color: #e3d286;
882
+ gap: 12px;
883
+ }
884
+
885
+ .mb-file-reload-msg {
886
+ display: flex;
887
+ align-items: center;
888
+ gap: 6px;
889
+ flex: 1;
890
+ min-width: 0;
891
+ overflow: hidden;
892
+ text-overflow: ellipsis;
893
+ white-space: nowrap;
894
+ }
895
+
896
+ .mb-file-reload-actions {
897
+ display: flex;
898
+ gap: 5px;
899
+ flex-shrink: 0;
900
+ }
901
+
902
+ .mb-btn-warning {
903
+ background: #6b4500;
904
+ color: #ffd88a;
905
+ border: 1px solid #8a5900;
906
+ }
907
+
908
+ .mb-btn-warning:hover {
909
+ background: #8a5900;
910
+ }
911
+
912
+ /* ── Merge Conflict Decorations ─────────────────────────────────── */
913
+ .mb-conflict-marker-line {
914
+ background: rgba(120, 80, 0, 0.35) !important;
915
+ }
916
+
917
+ .mb-conflict-head {
918
+ background: rgba(180, 60, 60, 0.18) !important;
919
+ border-left: 2px solid rgba(200, 80, 80, 0.6);
920
+ }
921
+
922
+ .mb-conflict-incoming {
923
+ background: rgba(60, 130, 60, 0.18) !important;
924
+ border-left: 2px solid rgba(80, 160, 80, 0.6);
925
+ }
926
+
927
+ /* ── Conflict Banner ─────────────────────────────────────────────── */
928
+ .mb-conflict-banner {
929
+ display: flex;
930
+ align-items: center;
931
+ gap: 10px;
932
+ padding: 5px 12px;
933
+ background: #1e1a00;
934
+ border-bottom: 1px solid #5a5000;
935
+ font-size: 12px;
936
+ color: #e3d286;
937
+ flex-shrink: 0;
938
+ }
939
+
940
+ .mb-conflict-count {
941
+ font-weight: bold;
942
+ color: #f5a623;
943
+ white-space: nowrap;
944
+ }
945
+
946
+ .mb-conflict-nav {
947
+ display: flex;
948
+ gap: 4px;
949
+ }
950
+
951
+ .mb-conflict-actions {
952
+ display: flex;
953
+ gap: 5px;
954
+ margin-left: auto;
955
+ }
956
+
957
+ .mb-btn-success {
958
+ background: #1a5c2a;
959
+ color: #aef;
960
+ border: 1px solid #2a7a3a;
961
+ }
962
+
963
+ .mb-btn-success:hover {
964
+ background: #1e7a38;
965
+ }
966
+
967
+ .mb-btn-incoming {
968
+ background: #1a3060;
969
+ color: #aef;
970
+ border: 1px solid #1a4080;
971
+ }
972
+
973
+ .mb-btn-incoming:hover {
974
+ background: #1a4080;
975
+ }