mbeditor 0.5.6 → 0.6.0

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.
@@ -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);
@@ -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.
@@ -58,33 +58,30 @@ html, body, #mbeditor-root {
58
58
 
59
59
  /* ── Sidebar ─────────────────────────────────────────────── */
60
60
  .ide-sidebar {
61
- width: 280px;
62
61
  min-width: 280px;
63
62
  max-width: 560px;
64
63
  background: var(--ide-panel-bg);
65
- border-right: 1px solid var(--ide-border);
66
64
  display: flex;
67
65
  flex-direction: column;
68
66
  overflow: hidden;
69
67
  flex-shrink: 0;
70
- transition: width 0.15s ease;
71
- }
72
-
73
- .ide-sidebar.ide-sidebar-collapsed {
74
- min-width: 0;
75
- max-width: none;
76
68
  }
77
69
 
78
- /* ── Collapsed sidebar icon strip ──────────────────────────── */
79
- .sidebar-icon-strip {
70
+ /* ── Activity Bar (VSCode-style, always visible) ───────── */
71
+ .ide-activity-bar {
72
+ width: 48px;
73
+ min-width: 48px;
74
+ flex-shrink: 0;
75
+ background: var(--ide-panel-bg);
76
+ border-right: 1px solid var(--ide-border);
80
77
  display: flex;
81
78
  flex-direction: column;
82
79
  align-items: center;
83
- height: 100%;
84
80
  padding: 4px 0;
81
+ z-index: 10;
85
82
  }
86
83
 
87
- .sidebar-strip-top {
84
+ .ide-activity-bar-top {
88
85
  display: flex;
89
86
  flex-direction: column;
90
87
  align-items: center;
@@ -92,7 +89,7 @@ html, body, #mbeditor-root {
92
89
  flex: 1;
93
90
  }
94
91
 
95
- .sidebar-strip-bottom {
92
+ .ide-activity-bar-bottom {
96
93
  display: flex;
97
94
  flex-direction: column;
98
95
  align-items: center;
@@ -100,7 +97,7 @@ html, body, #mbeditor-root {
100
97
  padding-bottom: 4px;
101
98
  }
102
99
 
103
- .sidebar-strip-btn {
100
+ .ide-activity-btn {
104
101
  width: 36px;
105
102
  height: 36px;
106
103
  background: transparent;
@@ -115,25 +112,28 @@ html, body, #mbeditor-root {
115
112
  transition: background 0.12s ease, color 0.12s ease;
116
113
  }
117
114
 
118
- .sidebar-strip-btn:hover {
115
+ .ide-activity-btn:hover {
119
116
  background: var(--ide-hover-bg);
120
117
  color: var(--ide-text);
121
118
  }
122
119
 
123
- .sidebar-strip-btn.active {
120
+ .ide-activity-btn.active {
124
121
  color: var(--ide-accent-fg);
125
122
  border-left: 2px solid var(--ide-accent-fg);
126
123
  }
127
124
 
128
- /* Visually groups Explorer + Search + Settings buttons in the collapsed strip */
129
- .sidebar-nav-group {
130
- display: flex;
131
- flex-direction: column;
132
- border: 1px solid var(--ide-border);
133
- border-radius: 4px;
134
- overflow: hidden;
125
+ .sidebar-panel-title {
126
+ font-size: 11px;
127
+ font-weight: 700;
128
+ text-transform: uppercase;
129
+ letter-spacing: 0.08em;
130
+ color: var(--ide-fg-muted, var(--ide-text-muted, #888));
131
+ padding: 10px 12px 6px;
132
+ flex-shrink: 0;
133
+ border-bottom: 1px solid var(--ide-border);
135
134
  }
136
135
 
136
+
137
137
  .panel-divider {
138
138
  flex-shrink: 0;
139
139
  cursor: col-resize;
@@ -146,13 +146,6 @@ html, body, #mbeditor-root {
146
146
  border-right: 1px solid var(--ide-border);
147
147
  }
148
148
 
149
- .ide-sidebar-collapsed + .sidebar-divider {
150
- cursor: default;
151
- }
152
-
153
- .ide-sidebar-collapsed + .sidebar-divider:hover {
154
- background: var(--ide-panel-bg-alt);
155
- }
156
149
 
157
150
  .pane-divider {
158
151
  width: 4px;
@@ -179,33 +172,6 @@ html, body, #mbeditor-root {
179
172
  gap: 6px;
180
173
  }
181
174
 
182
- .ide-sidebar-tabs {
183
- display: flex;
184
- border-bottom: 1px solid var(--ide-border);
185
- flex-shrink: 0;
186
- }
187
-
188
- .ide-sidebar-tab {
189
- flex: 1;
190
- padding: 5px 0;
191
- font-size: 11px;
192
- text-align: center;
193
- cursor: pointer;
194
- color: var(--ide-text-muted);
195
- border: none;
196
- border-radius: 0;
197
- background: transparent;
198
- transition: color 0.15s, background 0.15s;
199
- }
200
-
201
- .ide-sidebar-tab.ide-sidebar-tab-icon {
202
- flex: 0 0 auto;
203
- padding: 5px 10px;
204
- font-size: 14px;
205
- }
206
-
207
- .ide-sidebar-tab:hover { color: var(--ide-text); background: var(--ide-input-bg); }
208
- .ide-sidebar-tab.active { color: var(--ide-accent-fg); border-bottom: 2px solid var(--ide-accent-fg); }
209
175
 
210
176
  .ide-sidebar-content {
211
177
  flex: 1;
@@ -1069,6 +1035,76 @@ html, body, #mbeditor-root {
1069
1035
  gap: 6px;
1070
1036
  }
1071
1037
 
1038
+ /* ── Rails Panel ─────────────────────────────────────────── */
1039
+ .rails-panel {
1040
+ display: flex;
1041
+ flex-direction: column;
1042
+ overflow-y: auto;
1043
+ flex: 1;
1044
+ padding: 8px 0;
1045
+ }
1046
+
1047
+ .rails-panel-loading,
1048
+ .rails-panel-empty {
1049
+ padding: 12px 16px;
1050
+ color: var(--ide-text-muted);
1051
+ font-size: 12px;
1052
+ }
1053
+
1054
+ .rails-group {
1055
+ margin-bottom: 4px;
1056
+ }
1057
+
1058
+ .rails-group-title {
1059
+ padding: 4px 12px 2px;
1060
+ font-size: 10px;
1061
+ font-weight: 700;
1062
+ letter-spacing: 0.08em;
1063
+ text-transform: uppercase;
1064
+ color: var(--ide-text-muted);
1065
+ user-select: none;
1066
+ }
1067
+
1068
+ .rails-group-item {
1069
+ display: flex;
1070
+ align-items: center;
1071
+ gap: 6px;
1072
+ padding: 2px 12px 2px 16px;
1073
+ cursor: pointer;
1074
+ font-size: 12px;
1075
+ color: var(--ide-text);
1076
+ white-space: nowrap;
1077
+ overflow: hidden;
1078
+ text-overflow: ellipsis;
1079
+ }
1080
+
1081
+ .rails-group-item:hover {
1082
+ background: var(--ide-hover-bg);
1083
+ }
1084
+
1085
+ .rails-group-item-name {
1086
+ overflow: hidden;
1087
+ text-overflow: ellipsis;
1088
+ white-space: nowrap;
1089
+ flex: 1;
1090
+ min-width: 0;
1091
+ }
1092
+
1093
+ .rails-group-item-dirty {
1094
+ flex-shrink: 0;
1095
+ font-size: 8px;
1096
+ color: var(--ide-accent-fg);
1097
+ opacity: 0.85;
1098
+ line-height: 1;
1099
+ }
1100
+
1101
+ .rails-panel-overflow {
1102
+ padding: 4px 12px 8px;
1103
+ font-size: 11px;
1104
+ color: var(--ide-text-muted, #888);
1105
+ font-style: italic;
1106
+ }
1107
+
1072
1108
  /* ── Git Status Panel ─────────────────────────────────────── */
1073
1109
  .git-panel { padding: 8px; }
1074
1110
 
@@ -2109,14 +2145,6 @@ textarea {
2109
2145
  align-items: center;
2110
2146
  justify-content: center;
2111
2147
  }
2112
- .sidebar-strip-btn {
2113
- padding: 0;
2114
- margin: 0;
2115
- border: none;
2116
- display: inline-flex;
2117
- align-items: center;
2118
- justify-content: center;
2119
- }
2120
2148
 
2121
2149
  /* Context menu items */
2122
2150
  .context-menu-item {
@@ -2272,3 +2300,7 @@ button:not(.pico-btn) { margin-bottom: 0; }
2272
2300
  .project-actions[role="group"] button {
2273
2301
  --pico-form-element-spacing-horizontal: 0;
2274
2302
  }
2303
+
2304
+ .mbeditor-format-changed {
2305
+ background-color: rgba(80, 200, 100, 0.15);
2306
+ }
@@ -896,6 +896,16 @@ module Mbeditor
896
896
  render json: { error: e.message, ok: false }, status: :unprocessable_content
897
897
  end
898
898
 
899
+ # GET /mbeditor/related_files?path=...
900
+ def related_files
901
+ path = resolve_path(params[:path])
902
+ return render json: {}, status: :bad_request unless path
903
+ rel = relative_path(path)
904
+ custom = Array(Mbeditor.configuration.related_files_custom_paths)
905
+ result = RailsRelatedFilesService.find(workspace_root.to_s, rel, custom_paths: custom)
906
+ render json: result
907
+ end
908
+
899
909
  # POST /mbeditor/format — rubocop -A on buffer content; returns corrected content WITHOUT saving to disk
900
910
  #
901
911
  # Accepts the current buffer content as `code` and formats it using a
@@ -1090,22 +1100,25 @@ module Mbeditor
1090
1100
  0
1091
1101
  end
1092
1102
 
1093
- def build_tree(dir, max_depth: 10, depth: 0)
1103
+ def build_tree(dir, max_depth: 10, depth: 0, _excl: excluded_paths)
1094
1104
  return [] if depth >= max_depth
1095
1105
 
1096
1106
  entries = Dir.entries(dir).sort.reject { |entry| entry == "." || entry == ".." }
1097
1107
  entries.filter_map do |name|
1098
1108
  full = File.join(dir, name)
1099
1109
  rel = relative_path(full)
1110
+ is_excl = excluded_path?(rel, name, _excl)
1100
1111
 
1101
1112
  if File.directory?(full)
1102
- node = { name: name, type: "folder", path: rel, children: build_tree(full, depth: depth + 1) }
1103
- node[:excluded] = true if excluded_path?(rel, name)
1113
+ # Skip recursing into excluded directories avoids traversing node_modules etc.
1114
+ children = is_excl ? [] : build_tree(full, max_depth: max_depth, depth: depth + 1, _excl: _excl)
1115
+ node = { name: name, type: "folder", path: rel, children: children }
1116
+ node[:excluded] = true if is_excl
1104
1117
  node
1105
1118
  else
1106
- size = File.size(full) rescue nil
1107
- node = { name: name, type: "file", path: rel, size: size }
1108
- node[:excluded] = true if excluded_path?(rel, name)
1119
+ node = { name: name, type: "file", path: rel }
1120
+ node[:size] = (File.size(full) rescue nil) unless is_excl
1121
+ node[:excluded] = true if is_excl
1109
1122
  node
1110
1123
  end
1111
1124
  end
@@ -1125,8 +1138,8 @@ module Mbeditor
1125
1138
  Array(Mbeditor.configuration.ruby_def_include_dirs).map(&:to_s).reject(&:blank?)
1126
1139
  end
1127
1140
 
1128
- def excluded_path?(relative_path, name)
1129
- excluded_paths.any? do |pattern|
1141
+ def excluded_path?(relative_path, name, excl = excluded_paths)
1142
+ excl.any? do |pattern|
1130
1143
  if pattern.include?("/")
1131
1144
  relative_path == pattern || relative_path.start_with?("#{pattern}/")
1132
1145
  else
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mbeditor
4
+ # Given a workspace-relative file path, finds related Rails files grouped by
5
+ # type: controller, model, views, helper, tests, and custom directories.
6
+ #
7
+ # Only groups with at least one existing file are included in the result.
8
+ # All returned paths are workspace-relative strings.
9
+ module RailsRelatedFilesService
10
+ module_function
11
+
12
+ # Returns a hash of groups. Example:
13
+ #
14
+ # {
15
+ # controller: [{path: "app/controllers/users_controller.rb", name: "users_controller.rb"}],
16
+ # model: [{path: "app/models/user.rb", name: "user.rb"}],
17
+ # views: [{path: "app/views/users/index.html.erb", name: "index.html.erb"}],
18
+ # helper: [{path: "app/helpers/users_helper.rb", name: "users_helper.rb"}],
19
+ # tests: [{path: "test/controllers/users_controller_test.rb", name: "users_controller_test.rb"}],
20
+ # custom: {"app/assets/javascripts/app" => [{path: "...", name: "..."}]}
21
+ # }
22
+ #
23
+ # Returns {} when the path does not match any known Rails convention.
24
+ def find(workspace_root, relative_path, custom_paths: [])
25
+ plural, singular = extract_resource_names(relative_path)
26
+ return {} unless plural && singular
27
+
28
+ result = {}
29
+
30
+ # ── controller ────────────────────────────────────────────────────────────
31
+ controller_path = "app/controllers/#{plural}_controller.rb"
32
+ if file_exists?(workspace_root, controller_path)
33
+ result[:controller] = [entry(controller_path)]
34
+ end
35
+
36
+ # ── model ─────────────────────────────────────────────────────────────────
37
+ model_path = "app/models/#{singular}.rb"
38
+ if file_exists?(workspace_root, model_path)
39
+ result[:model] = [entry(model_path)]
40
+ end
41
+
42
+ # ── views ─────────────────────────────────────────────────────────────────
43
+ views_dir = File.join(workspace_root, "app", "views", plural)
44
+ if File.directory?(views_dir)
45
+ children = dir_children(workspace_root, "app/views/#{plural}")
46
+ result[:views] = children unless children.empty?
47
+ end
48
+
49
+ # ── helper ────────────────────────────────────────────────────────────────
50
+ helper_path = "app/helpers/#{plural}_helper.rb"
51
+ if file_exists?(workspace_root, helper_path)
52
+ result[:helper] = [entry(helper_path)]
53
+ end
54
+
55
+ # ── tests ─────────────────────────────────────────────────────────────────
56
+ test_candidates = [
57
+ "test/controllers/#{plural}_controller_test.rb",
58
+ "test/models/#{singular}_test.rb",
59
+ "spec/controllers/#{plural}_controller_spec.rb",
60
+ "spec/models/#{singular}_spec.rb"
61
+ ]
62
+ tests = test_candidates.select { |p| file_exists?(workspace_root, p) }.map { |p| entry(p) }
63
+ result[:tests] = tests unless tests.empty?
64
+
65
+ # ── custom paths ──────────────────────────────────────────────────────────
66
+ custom_result = {}
67
+ Array(custom_paths).each do |base|
68
+ base = base.to_s.strip
69
+ next if base.empty?
70
+
71
+ [plural, singular].uniq.each do |name|
72
+ rel_dir = "#{base}/#{name}"
73
+ abs_dir = File.join(workspace_root, rel_dir)
74
+ next unless File.directory?(abs_dir)
75
+
76
+ begin
77
+ real_ws = File.realpath(workspace_root)
78
+ real_dir = File.realpath(abs_dir)
79
+ next unless real_dir.start_with?("#{real_ws}/") || real_dir == real_ws
80
+ rescue Errno::ENOENT, Errno::EACCES
81
+ next
82
+ end
83
+
84
+ children = dir_children(workspace_root, rel_dir)
85
+ next if children.empty?
86
+
87
+ custom_result[base] ||= []
88
+ custom_result[base].concat(children)
89
+ end
90
+ end
91
+ result[:custom] = custom_result unless custom_result.empty?
92
+
93
+ result
94
+ end
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Helpers
98
+ # ---------------------------------------------------------------------------
99
+
100
+ # Returns [plural, singular] workspace-relative resource name strings
101
+ # (may include namespace prefix, e.g. "admin/users", "admin/user"),
102
+ # or nil when the path does not match a known Rails convention.
103
+ def extract_resource_names(relative_path)
104
+ parts = relative_path.to_s.split("/")
105
+ return nil unless parts.length >= 2
106
+
107
+ case parts[0]
108
+ when "app"
109
+ case parts[1]
110
+ when "controllers"
111
+ # app/controllers/[namespace/]name_controller.rb
112
+ return nil unless parts.last.end_with?("_controller.rb")
113
+
114
+ ns_and_file = parts[2..]
115
+ file = ns_and_file.last
116
+ ns = ns_and_file[0..-2] # may be empty
117
+ base = file.delete_suffix("_controller.rb")
118
+ plural = (ns + [base]).join("/")
119
+ singular = (ns + [singularize(base)]).join("/")
120
+ [plural, singular]
121
+
122
+ when "models"
123
+ # app/models/[namespace/]name.rb
124
+ return nil unless parts.last.end_with?(".rb")
125
+
126
+ ns_and_file = parts[2..]
127
+ file = ns_and_file.last.delete_suffix(".rb")
128
+ ns = ns_and_file[0..-2]
129
+ singular = (ns + [file]).join("/")
130
+ plural = (ns + [pluralize(file)]).join("/")
131
+ [plural, singular]
132
+
133
+ when "views"
134
+ # app/views/[namespace/]resource/anything
135
+ return nil unless parts.length >= 4
136
+
137
+ # All path segments between "views" and the view filename form the resource path.
138
+ # e.g. app/views/admin/users/index.html.erb → view_resource = ["admin","users"]
139
+ # e.g. app/views/users/index.html.erb → view_resource = ["users"]
140
+ view_resource = parts[2..parts.length - 2]
141
+ plural = view_resource.join("/")
142
+ singular = (view_resource[0..-2] + [singularize(view_resource.last)]).join("/")
143
+ [plural, singular]
144
+
145
+ when "helpers"
146
+ # app/helpers/[namespace/]name_helper.rb
147
+ return nil unless parts.last.end_with?("_helper.rb")
148
+
149
+ ns_and_file = parts[2..]
150
+ file = ns_and_file.last
151
+ ns = ns_and_file[0..-2]
152
+ base = file.delete_suffix("_helper.rb")
153
+ plural = (ns + [base]).join("/")
154
+ singular = (ns + [singularize(base)]).join("/")
155
+ [plural, singular]
156
+
157
+ else
158
+ nil
159
+ end
160
+
161
+ when "test", "spec"
162
+ framework = parts[0]
163
+ case parts[1]
164
+ when "controllers"
165
+ # test/controllers/[ns/]name_controller_test.rb
166
+ suffix = framework == "test" ? "_controller_test.rb" : "_controller_spec.rb"
167
+ return nil unless parts.last.end_with?(suffix)
168
+
169
+ ns_and_file = parts[2..]
170
+ file = ns_and_file.last
171
+ ns = ns_and_file[0..-2]
172
+ base = file.delete_suffix(suffix)
173
+ plural = (ns + [base]).join("/")
174
+ singular = (ns + [singularize(base)]).join("/")
175
+ [plural, singular]
176
+
177
+ when "models"
178
+ # test/models/[ns/]name_test.rb
179
+ suffix = framework == "test" ? "_test.rb" : "_spec.rb"
180
+ return nil unless parts.last.end_with?(suffix)
181
+
182
+ ns_and_file = parts[2..]
183
+ file = ns_and_file.last
184
+ ns = ns_and_file[0..-2]
185
+ base = file.delete_suffix(suffix)
186
+ singular = (ns + [base]).join("/")
187
+ plural = (ns + [pluralize(base)]).join("/")
188
+ [plural, singular]
189
+
190
+ else
191
+ nil
192
+ end
193
+
194
+ else
195
+ nil
196
+ end
197
+ end
198
+
199
+ def entry(rel_path)
200
+ { path: rel_path, name: File.basename(rel_path), type: :file }
201
+ end
202
+
203
+ def file_exists?(workspace_root, rel_path)
204
+ File.exist?(File.join(workspace_root, rel_path))
205
+ end
206
+
207
+ # Returns direct file children (not subdirectories) of a workspace-relative
208
+ # directory, sorted by name, as {path:, name:, type: :file} hashes.
209
+ def dir_children(workspace_root, rel_dir)
210
+ abs_dir = File.join(workspace_root, rel_dir)
211
+ return [] unless File.directory?(abs_dir)
212
+
213
+ Dir.entries(abs_dir)
214
+ .reject { |n| n.start_with?(".") }
215
+ .sort
216
+ .filter_map do |name|
217
+ full = File.join(abs_dir, name)
218
+ next unless File.file?(full)
219
+
220
+ { path: "#{rel_dir}/#{name}", name: name, type: :file }
221
+ end
222
+ end
223
+
224
+ def singularize(word)
225
+ ActiveSupport::Inflector.singularize(word)
226
+ end
227
+
228
+ def pluralize(word)
229
+ ActiveSupport::Inflector.pluralize(word)
230
+ end
231
+
232
+ private_class_method :entry, :file_exists?, :dir_children, :singularize, :pluralize, :extract_resource_names
233
+ end
234
+ end
data/config/routes.rb CHANGED
@@ -26,6 +26,7 @@ Mbeditor::Engine.routes.draw do
26
26
  get 'module_members', to: 'editors#module_members'
27
27
  get 'file_includes', to: 'editors#file_includes'
28
28
  get 'unused_methods', to: 'editors#unused_methods'
29
+ get 'related_files', to: 'editors#related_files'
29
30
  get 'git_info', to: 'editors#git_info'
30
31
  get 'git_status', to: 'editors#git_status'
31
32
  get 'manifest.webmanifest', to: 'editors#pwa_manifest', format: false
@@ -7,7 +7,7 @@ module Mbeditor
7
7
  :test_framework, :test_command, :test_timeout,
8
8
  :authenticate_with,
9
9
  :lint_timeout, :base_branch_candidates, :git_timeout,
10
- :ruby_def_include_dirs
10
+ :ruby_def_include_dirs, :related_files_custom_paths
11
11
 
12
12
  def initialize
13
13
  @allowed_environments = [:development]
@@ -25,6 +25,7 @@ module Mbeditor
25
25
  @base_branch_candidates = %w[origin/develop origin/main origin/master develop main master]
26
26
  @git_timeout = nil # seconds; nil disables (no timeout on git subprocesses)
27
27
  @ruby_def_include_dirs = %w[app/models app/controllers app/helpers app/concerns]
28
+ @related_files_custom_paths = []
28
29
  end
29
30
  end
30
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbeditor
4
- VERSION = "0.5.6"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-04 00:00:00.000000000 Z
11
+ date: 2026-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -94,6 +94,7 @@ files:
94
94
  - app/services/mbeditor/git_service.rb
95
95
  - app/services/mbeditor/js_definition_service.rb
96
96
  - app/services/mbeditor/js_members_service.rb
97
+ - app/services/mbeditor/rails_related_files_service.rb
97
98
  - app/services/mbeditor/redmine_service.rb
98
99
  - app/services/mbeditor/ri_definition_service.rb
99
100
  - app/services/mbeditor/ruby_definition_service.rb