mbeditor 0.5.1 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6e34c84c4c7e3a0ef9d4382dd49c29ac3bd13fdc42f8b38d50666cdd30439b6
4
- data.tar.gz: 07a9658a8d2faa837969d7c8b05556eaba59afeb48af63b9c8482d7693805b64
3
+ metadata.gz: cd0837fbeccc3634d7804e6f9957bbbaca06bb4c02942eb4decc5e8b174499ab
4
+ data.tar.gz: 9f97239369b2968c17cd3909abdced4917329a349564d02ebde6beb89cb8c36f
5
5
  SHA512:
6
- metadata.gz: 4506a5f5c0e7d7c5b111f9e6cb665cf5d95b7a2e9be5c140948c49f3fbb3a7ee9c13e78d1924d891522131cb85d512242074202298172a10915ea57e6e48f71a
7
- data.tar.gz: c7b06da62965639c18f0531a9459da9ddcfeb14ba29ecceadeb600aeec8eb28197657c3dcca4dec5311978fef1397dc610cc2d9f27ead2513ec2e768daceda62
6
+ metadata.gz: 6310dc010236970bf25c0945a465122e1f8a081c399696d8870dc852d85454153863b01b1f1e9a8871abc3fbe07b1489724a065835c2909daa15426899701833
7
+ data.tar.gz: f8ed953601769d6785a4fecd4c3b16a6ce820e6e7f5c88a7cb12750d8f32b5122dc6bc25fd67be5e86bd7ef02ae1beba9ece2dbc5fc5bc0324df47785a03c9ba
@@ -12,6 +12,13 @@ var useRef = _React.useRef;
12
12
  var useEffect = _React.useEffect;
13
13
  var useMemo = _React.useMemo;
14
14
 
15
+ function formatSize(bytes) {
16
+ if (typeof bytes !== 'number' || bytes < 0) return '';
17
+ if (bytes < 1024) return bytes + ' B';
18
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
19
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
20
+ }
21
+
15
22
  var FileTree = function FileTree(_ref) {
16
23
  var items = _ref.items;
17
24
  var onSelect = _ref.onSelect;
@@ -477,7 +484,7 @@ var FileTree = function FileTree(_ref) {
477
484
  ),
478
485
  React.createElement(
479
486
  'div',
480
- { className: 'tree-item-name', title: node.path },
487
+ { className: 'tree-item-name', title: node.type === 'file' && node.size != null ? node.path + ' — ' + formatSize(node.size) : node.path },
481
488
  node.name
482
489
  ),
483
490
  statusMeta && React.createElement(
@@ -51,12 +51,16 @@
51
51
 
52
52
  var globalsRegistered = false;
53
53
 
54
- // Enumerate window for user-defined (non-native) globals and return a TypeScript
55
- // declaration string. Sprockets exposes every top-level var/function as a window
56
- // property before Monaco initialises, so scanning window at registration time
57
- // captures components, services, and helpers without any manual listing.
54
+ // Enumerate window for user-defined globals and return a TypeScript declaration string.
55
+ // Sprockets exposes every top-level var/function as a window property before Monaco
56
+ // initialises, so scanning at registration time captures all components and helpers.
57
+ //
58
+ // Filter: keep only plain writable data properties (configurable, writable, no getter).
59
+ // Browser built-ins are either non-configurable or accessor properties (hasGet), so
60
+ // this reliably separates them from user-assigned globals without a native-code test
61
+ // (which only works for functions, not objects like `document` or `location`).
58
62
  function buildWindowGlobalsShim() {
59
- var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1 };
63
+ var alreadyDeclared = { React: 1, ReactDOM: 1, PropTypes: 1, MaterialUI: 1, $: 1, jQuery: 1 };
60
64
  var lines = [];
61
65
  try {
62
66
  var keys = Object.keys(window);
@@ -64,14 +68,12 @@
64
68
  var key = keys[i];
65
69
  if (alreadyDeclared[key]) continue;
66
70
  if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) continue;
71
+ var desc;
72
+ try { desc = Object.getOwnPropertyDescriptor(window, key); } catch (e) { continue; }
73
+ if (!desc || !desc.configurable || !desc.writable || desc.get) continue;
67
74
  var value;
68
75
  try { value = window[key]; } catch (e) { continue; }
69
76
  if (value === null || value === undefined) continue;
70
- if (typeof value === 'function') {
71
- try {
72
- if (/\[native code\]/.test(Function.prototype.toString.call(value))) continue;
73
- } catch (e) { continue; }
74
- }
75
77
  lines.push('declare var ' + key + ': any;');
76
78
  }
77
79
  } catch (e) {}
@@ -322,6 +324,30 @@
322
324
  event.stopPropagation();
323
325
  });
324
326
 
327
+ // Navigate to a Ruby symbol: modules/classes go to their definition file,
328
+ // lowercase symbols go to their def line.
329
+ function navigateToWord(word) {
330
+ if (/^[A-Z]/.test(word) && typeof FileService !== 'undefined' && FileService.getModuleMembers) {
331
+ FileService.getModuleMembers(word).then(function(data) {
332
+ if (!data || !data.file) return;
333
+ var filename = data.file.split('/').pop();
334
+ if (typeof TabManager !== 'undefined' && TabManager.openTab) {
335
+ TabManager.openTab(data.file, filename, 1);
336
+ }
337
+ }).catch(function() {});
338
+ return;
339
+ }
340
+ if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
341
+ FileService.getDefinition(word, 'ruby').then(function(data) {
342
+ var results = data && Array.isArray(data.results) ? data.results : [];
343
+ if (results.length === 0) return;
344
+ var r = results[0];
345
+ if (typeof TabManager !== 'undefined' && TabManager.openTab) {
346
+ TabManager.openTab(r.file, r.file.split('/').pop(), r.line);
347
+ }
348
+ }).catch(function() {});
349
+ }
350
+
325
351
  // Ctrl/Cmd+click — navigate to definition
326
352
  gotoMouseDisposable = editor.onMouseDown(function(event) {
327
353
  var ctrlOrCmd = event.event.ctrlKey || event.event.metaKey;
@@ -336,19 +362,9 @@
336
362
  if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
337
363
  if (RUBY_KEYWORDS[wordInfo.word]) return;
338
364
  if (RUBY_CORE_METHODS[wordInfo.word]) return;
339
- if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
340
365
 
341
366
  event.event.preventDefault();
342
-
343
- FileService.getDefinition(wordInfo.word, 'ruby').then(function(data) {
344
- var results = data && Array.isArray(data.results) ? data.results : [];
345
- if (results.length === 0) return;
346
- var r = results[0];
347
- var filename = r.file.split('/').pop();
348
- if (typeof TabManager !== 'undefined' && TabManager.openTab) {
349
- TabManager.openTab(r.file, filename, r.line);
350
- }
351
- }).catch(function() {});
367
+ navigateToWord(wordInfo.word);
352
368
  });
353
369
 
354
370
  // F12 — go to definition from keyboard
@@ -365,17 +381,7 @@
365
381
  if (!wordInfo || !wordInfo.word || wordInfo.word.length < 2) return;
366
382
  if (RUBY_KEYWORDS[wordInfo.word]) return;
367
383
  if (RUBY_CORE_METHODS[wordInfo.word]) return;
368
- if (typeof FileService === 'undefined' || !FileService.getDefinition) return;
369
-
370
- FileService.getDefinition(wordInfo.word, 'ruby').then(function(data) {
371
- var results = data && Array.isArray(data.results) ? data.results : [];
372
- if (results.length === 0) return;
373
- var r = results[0];
374
- var filename = r.file.split('/').pop();
375
- if (typeof TabManager !== 'undefined' && TabManager.openTab) {
376
- TabManager.openTab(r.file, filename, r.line);
377
- }
378
- }).catch(function() {});
384
+ navigateToWord(wordInfo.word);
379
385
  }
380
386
  });
381
387
  }
@@ -494,6 +500,8 @@
494
500
  'declare var ReactDOM: any;',
495
501
  'declare var PropTypes: any;',
496
502
  'declare var MaterialUI: any;',
503
+ 'declare var $: any;',
504
+ 'declare var jQuery: any;',
497
505
  'interface Window { [key: string]: any; }'
498
506
  ].join('\n'),
499
507
  'inmemory://mbeditor/sprockets-globals.d.ts'
@@ -506,6 +514,38 @@
506
514
  'inmemory://mbeditor/window-globals.d.ts'
507
515
  );
508
516
  }
517
+
518
+ // Downgrade "declared but never read" (TS6133) from Error to Warning.
519
+ // TypeScript has no built-in way to emit this as a warning, so we intercept
520
+ // the marker set after the worker fires and re-apply with lower severity.
521
+ // JS files use owner 'javascript', TS files use 'typescript'.
522
+ var WARN_CODES = { '6133': true };
523
+ var TS_OWNERS = ['javascript', 'typescript'];
524
+ var _severityPatchActive = false;
525
+ monaco.editor.onDidChangeMarkers(function(uris) {
526
+ if (_severityPatchActive) return;
527
+ _severityPatchActive = true;
528
+ try {
529
+ uris.forEach(function(uri) {
530
+ var model = monaco.editor.getModel(uri);
531
+ if (!model) return;
532
+ TS_OWNERS.forEach(function(owner) {
533
+ var markers = monaco.editor.getModelMarkers({ resource: uri, owner: owner });
534
+ var needsPatch = markers.some(function(m) {
535
+ return m.severity === monaco.MarkerSeverity.Error && WARN_CODES[String(m.code)];
536
+ });
537
+ if (!needsPatch) return;
538
+ monaco.editor.setModelMarkers(model, owner, markers.map(function(m) {
539
+ return (m.severity === monaco.MarkerSeverity.Error && WARN_CODES[String(m.code)])
540
+ ? Object.assign({}, m, { severity: monaco.MarkerSeverity.Warning })
541
+ : m;
542
+ }));
543
+ });
544
+ });
545
+ } finally {
546
+ _severityPatchActive = false;
547
+ }
548
+ });
509
549
  }
510
550
 
511
551
  // TypeScript: enable JSX for .tsx files and catch unused locals.
@@ -337,7 +337,8 @@ module Mbeditor
337
337
  workspace_root,
338
338
  symbol,
339
339
  excluded_dirnames: excluded_dirnames,
340
- excluded_paths: excluded_paths
340
+ excluded_paths: excluded_paths,
341
+ included_dirs: ruby_def_include_dirs
341
342
  )
342
343
  ri = RiDefinitionService.call(symbol)
343
344
  workspace + ri
@@ -360,7 +361,8 @@ module Mbeditor
360
361
  file = RubyDefinitionService.module_defined_in(
361
362
  workspace_root, name,
362
363
  excluded_dirnames: excluded_dirnames,
363
- excluded_paths: excluded_paths
364
+ excluded_paths: excluded_paths,
365
+ included_dirs: ruby_def_include_dirs
364
366
  )
365
367
  return render json: { name: name, methods: [] } unless file
366
368
 
@@ -383,14 +385,16 @@ module Mbeditor
383
385
  # Fast no-op on subsequent calls (mtime checks only).
384
386
  RubyDefinitionService.scan(workspace_root,
385
387
  excluded_dirnames: excluded_dirnames,
386
- excluded_paths: excluded_paths)
388
+ excluded_paths: excluded_paths,
389
+ included_dirs: ruby_def_include_dirs)
387
390
 
388
391
  module_names = RubyDefinitionService.includes_in_file(path)
389
392
  includes = module_names.filter_map do |mod_name|
390
393
  mod_file = RubyDefinitionService.module_defined_in(
391
394
  workspace_root, mod_name,
392
395
  excluded_dirnames: excluded_dirnames,
393
- excluded_paths: excluded_paths
396
+ excluded_paths: excluded_paths,
397
+ included_dirs: ruby_def_include_dirs
394
398
  )
395
399
  next unless mod_file
396
400
 
@@ -1068,7 +1072,8 @@ module Mbeditor
1068
1072
  if File.directory?(full)
1069
1073
  { name: name, type: "folder", path: rel, children: build_tree(full, depth: depth + 1) }
1070
1074
  else
1071
- { name: name, type: "file", path: rel }
1075
+ size = File.size(full) rescue nil
1076
+ { name: name, type: "file", path: rel, size: size }
1072
1077
  end
1073
1078
  end
1074
1079
  rescue Errno::EACCES
@@ -1083,6 +1088,10 @@ module Mbeditor
1083
1088
  excluded_paths.filter { |path| !path.include?("/") }
1084
1089
  end
1085
1090
 
1091
+ def ruby_def_include_dirs
1092
+ Array(Mbeditor.configuration.ruby_def_include_dirs).map(&:to_s).reject(&:blank?)
1093
+ end
1094
+
1086
1095
  def excluded_path?(relative_path, name)
1087
1096
  excluded_paths.any? do |pattern|
1088
1097
  if pattern.include?("/")
@@ -51,10 +51,11 @@ module Mbeditor
51
51
  attr_reader :file_cache, :mutex
52
52
  attr_accessor :cache_path
53
53
 
54
- def call(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [])
54
+ def call(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
55
55
  new(workspace_root, symbol,
56
56
  excluded_dirnames: excluded_dirnames,
57
- excluded_paths: excluded_paths).call
57
+ excluded_paths: excluded_paths,
58
+ included_dirs: included_dirs).call
58
59
  end
59
60
 
60
61
  # Load the JSON cache from disk exactly once per process (double-checked
@@ -122,20 +123,26 @@ module Mbeditor
122
123
  # Searches the cache (and triggers a workspace scan if needed) to find
123
124
  # which file in +workspace_root+ defines the given module or class name.
124
125
  # Returns the absolute file path string or nil.
125
- def module_defined_in(workspace_root, module_name, excluded_dirnames: [], excluded_paths: [])
126
+ def module_defined_in(workspace_root, module_name, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
126
127
  load_disk_cache_once
128
+ root_prefix = workspace_root.to_s.chomp("/")
129
+ within_dirs = ->(path) {
130
+ return true if included_dirs.empty?
131
+ included_dirs.any? { |d| path.start_with?(File.join(root_prefix, d) + "/") }
132
+ }
127
133
  result = @mutex.synchronize do
128
- @file_cache.find { |_path, entry| entry[:module_names]&.include?(module_name) }
134
+ @file_cache.find { |path, entry| within_dirs.call(path) && entry[:module_names]&.include?(module_name) }
129
135
  end
130
136
  return result[0] if result
131
137
 
132
138
  # Cache miss: scan workspace to populate cache entries with module_names.
133
139
  new(workspace_root, nil,
134
140
  excluded_dirnames: excluded_dirnames,
135
- excluded_paths: excluded_paths).scan_workspace
141
+ excluded_paths: excluded_paths,
142
+ included_dirs: included_dirs).scan_workspace
136
143
 
137
144
  result = @mutex.synchronize do
138
- @file_cache.find { |_path, entry| entry[:module_names]&.include?(module_name) }
145
+ @file_cache.find { |path, entry| within_dirs.call(path) && entry[:module_names]&.include?(module_name) }
139
146
  end
140
147
  result ? result[0] : nil
141
148
  end
@@ -153,18 +160,20 @@ module Mbeditor
153
160
 
154
161
  # Convenience wrapper: scan the whole workspace to warm the cache.
155
162
  # Fast on subsequent calls (only re-parses files whose mtime changed).
156
- def scan(workspace_root, excluded_dirnames: [], excluded_paths: [])
163
+ def scan(workspace_root, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
157
164
  new(workspace_root, nil,
158
165
  excluded_dirnames: excluded_dirnames,
159
- excluded_paths: excluded_paths).scan_workspace
166
+ excluded_paths: excluded_paths,
167
+ included_dirs: included_dirs).scan_workspace
160
168
  end
161
169
  end
162
170
 
163
- def initialize(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [])
164
- @workspace_root = workspace_root.to_s.chomp("/")
165
- @symbol = symbol
171
+ def initialize(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [], included_dirs: [])
172
+ @workspace_root = workspace_root.to_s.chomp("/")
173
+ @symbol = symbol
166
174
  @excluded_dirnames = Array(excluded_dirnames)
167
175
  @excluded_paths = Array(excluded_paths)
176
+ @included_dirs = Array(included_dirs)
168
177
  end
169
178
 
170
179
  # Walks the entire workspace and populates the per-file cache (including the
@@ -176,27 +185,29 @@ module Mbeditor
176
185
  files_scanned = 0
177
186
  evict_deleted_cache_entries
178
187
 
179
- Find.find(@workspace_root) do |path|
180
- if File.directory?(path)
181
- dirname = File.basename(path)
182
- rel_dir = relative_path(path)
183
- Find.prune if path != @workspace_root && excluded_dir?(dirname, rel_dir)
184
- next
185
- end
186
- next unless path.end_with?(".rb")
188
+ search_roots.each do |root|
189
+ Find.find(root) do |path|
190
+ if File.directory?(path)
191
+ dirname = File.basename(path)
192
+ rel_dir = relative_path(path)
193
+ Find.prune if path != root && excluded_dir?(dirname, rel_dir)
194
+ next
195
+ end
196
+ next unless path.end_with?(".rb")
187
197
 
188
- rel = relative_path(path)
189
- next if excluded_rel_path?(rel, File.basename(path))
198
+ rel = relative_path(path)
199
+ next if excluded_rel_path?(rel, File.basename(path))
190
200
 
191
- files_scanned += 1
192
- if files_scanned > MAX_FILES_SCANNED
193
- Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
194
- break
195
- end
196
- begin
197
- cache_entry_for(path)
198
- rescue StandardError
199
- nil
201
+ files_scanned += 1
202
+ if files_scanned > MAX_FILES_SCANNED
203
+ Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
204
+ break
205
+ end
206
+ begin
207
+ cache_entry_for(path)
208
+ rescue StandardError
209
+ nil
210
+ end
200
211
  end
201
212
  end
202
213
 
@@ -212,46 +223,46 @@ module Mbeditor
212
223
 
213
224
  evict_deleted_cache_entries
214
225
 
215
- Find.find(@workspace_root) do |path|
216
- # Prune excluded directories
217
- if File.directory?(path)
218
- dirname = File.basename(path)
219
- rel_dir = relative_path(path)
220
- if path != @workspace_root && excluded_dir?(dirname, rel_dir)
221
- Find.prune
226
+ search_roots.each do |root|
227
+ Find.find(root) do |path|
228
+ # Prune excluded directories
229
+ if File.directory?(path)
230
+ dirname = File.basename(path)
231
+ rel_dir = relative_path(path)
232
+ Find.prune if path != root && excluded_dir?(dirname, rel_dir)
233
+ next
222
234
  end
223
- next
224
- end
225
235
 
226
- next unless path.end_with?(".rb")
236
+ next unless path.end_with?(".rb")
227
237
 
228
- rel = relative_path(path)
229
- next if excluded_rel_path?(rel, File.basename(path))
230
-
231
- files_scanned += 1
232
- if files_scanned > MAX_FILES_SCANNED
233
- Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
234
- break
235
- end
238
+ rel = relative_path(path)
239
+ next if excluded_rel_path?(rel, File.basename(path))
236
240
 
237
- begin
238
- cached = cache_entry_for(path)
239
- next unless cached
240
-
241
- hit_lines = @symbol ? cached[:all_defs].fetch(@symbol, nil) : nil
242
- next unless hit_lines && hit_lines.any?
241
+ files_scanned += 1
242
+ if files_scanned > MAX_FILES_SCANNED
243
+ Rails.logger.warn("[mbeditor] RubyDefinitionService: workspace exceeds #{MAX_FILES_SCANNED} .rb files; stopping scan early")
244
+ break
245
+ end
243
246
 
244
- hit_lines.each do |def_line|
245
- results << {
246
- file: rel,
247
- line: def_line,
248
- signature: (cached[:lines][def_line - 1] || "").strip,
249
- comments: extract_comments(cached[:lines], def_line)
250
- }
251
- return results if results.length >= MAX_RESULTS
247
+ begin
248
+ cached = cache_entry_for(path)
249
+ next unless cached
250
+
251
+ hit_lines = @symbol ? cached[:all_defs].fetch(@symbol, nil) : nil
252
+ next unless hit_lines && hit_lines.any?
253
+
254
+ hit_lines.each do |def_line|
255
+ results << {
256
+ file: rel,
257
+ line: def_line,
258
+ signature: (cached[:lines][def_line - 1] || "").strip,
259
+ comments: extract_comments(cached[:lines], def_line)
260
+ }
261
+ return results if results.length >= MAX_RESULTS
262
+ end
263
+ rescue StandardError
264
+ # Malformed file or unreadable; skip silently
252
265
  end
253
- rescue StandardError
254
- # Malformed file or unreadable; skip silently
255
266
  end
256
267
  end
257
268
 
@@ -386,6 +397,15 @@ module Mbeditor
386
397
  full_path.to_s.delete_prefix(@workspace_root).delete_prefix("/")
387
398
  end
388
399
 
400
+ def search_roots
401
+ return [@workspace_root] if @included_dirs.empty?
402
+
403
+ dirs = @included_dirs
404
+ .map { |d| File.join(@workspace_root, d) }
405
+ .select { |d| File.directory?(d) }
406
+ dirs.empty? ? [@workspace_root] : dirs
407
+ end
408
+
389
409
  def excluded_dir?(dirname, rel_dir)
390
410
  @excluded_dirnames.include?(dirname) ||
391
411
  @excluded_paths.any? do |pattern|
@@ -6,7 +6,8 @@ module Mbeditor
6
6
  :redmine_enabled, :redmine_url, :redmine_api_key, :redmine_ticket_source,
7
7
  :test_framework, :test_command, :test_timeout,
8
8
  :authenticate_with,
9
- :lint_timeout, :base_branch_candidates, :git_timeout
9
+ :lint_timeout, :base_branch_candidates, :git_timeout,
10
+ :ruby_def_include_dirs
10
11
 
11
12
  def initialize
12
13
  @allowed_environments = [:development]
@@ -22,7 +23,8 @@ module Mbeditor
22
23
  @test_timeout = 60 # seconds
23
24
  @lint_timeout = 15 # seconds for RuboCop/haml-lint subprocesses
24
25
  @base_branch_candidates = %w[origin/develop origin/main origin/master develop main master]
25
- @git_timeout = nil # seconds; nil disables (no timeout on git subprocesses)
26
+ @git_timeout = nil # seconds; nil disables (no timeout on git subprocesses)
27
+ @ruby_def_include_dirs = %w[app/models app/controllers app/helpers app/concerns]
26
28
  end
27
29
  end
28
30
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbeditor
4
- VERSION = "0.5.1"
4
+ VERSION = "0.5.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan