mbeditor 0.3.9 → 0.4.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 +4 -4
- data/CHANGELOG.md +25 -0
- data/app/assets/javascripts/mbeditor/application.js +1 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +161 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +392 -62
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +20 -4
- data/app/assets/javascripts/mbeditor/components/TabBar.js +3 -2
- data/app/assets/javascripts/mbeditor/editor_plugins.js +21 -0
- data/app/assets/javascripts/mbeditor/file_service.js +6 -0
- data/app/assets/javascripts/mbeditor/search_service.js +7 -3
- data/app/assets/javascripts/mbeditor/websocket_service.js +126 -0
- data/app/assets/stylesheets/mbeditor/editor.css +225 -10
- data/app/channels/mbeditor/editor_channel.rb +79 -0
- data/app/controllers/mbeditor/editors_controller.rb +48 -13
- data/app/views/layouts/mbeditor/application.html.erb +4 -0
- data/lib/mbeditor/cable_log_filter.rb +28 -0
- data/lib/mbeditor/engine.rb +10 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +4 -1
- data/lib/mbeditor/version.rb +1 -1
- metadata +5 -2
|
@@ -38,7 +38,8 @@ module Mbeditor
|
|
|
38
38
|
gitAvailable: git_available?,
|
|
39
39
|
blameAvailable: git_blame_available?,
|
|
40
40
|
redmineEnabled: Mbeditor.configuration.redmine_enabled == true,
|
|
41
|
-
testAvailable: test_available
|
|
41
|
+
testAvailable: test_available?,
|
|
42
|
+
actionCableEnabled: defined?(ActionCable::Channel::Base) ? true : false
|
|
42
43
|
}
|
|
43
44
|
end
|
|
44
45
|
|
|
@@ -206,6 +207,7 @@ module Mbeditor
|
|
|
206
207
|
return render_file_too_large(content.bytesize) if content.bytesize > MAX_OPEN_FILE_SIZE_BYTES
|
|
207
208
|
|
|
208
209
|
File.write(path, content)
|
|
210
|
+
broadcast_files_changed
|
|
209
211
|
render json: { ok: true, path: relative_path(path) }
|
|
210
212
|
rescue StandardError => e
|
|
211
213
|
render json: { error: e.message }, status: :unprocessable_content
|
|
@@ -223,6 +225,7 @@ module Mbeditor
|
|
|
223
225
|
|
|
224
226
|
FileUtils.mkdir_p(File.dirname(path))
|
|
225
227
|
File.write(path, content)
|
|
228
|
+
broadcast_files_changed
|
|
226
229
|
|
|
227
230
|
render json: { ok: true, type: "file", path: relative_path(path), name: File.basename(path) }
|
|
228
231
|
rescue StandardError => e
|
|
@@ -237,6 +240,7 @@ module Mbeditor
|
|
|
237
240
|
return render json: { error: "Path already exists" }, status: :unprocessable_content if File.exist?(path)
|
|
238
241
|
|
|
239
242
|
FileUtils.mkdir_p(path)
|
|
243
|
+
broadcast_files_changed
|
|
240
244
|
render json: { ok: true, type: "folder", path: relative_path(path), name: File.basename(path) }
|
|
241
245
|
rescue StandardError => e
|
|
242
246
|
render json: { error: e.message }, status: :unprocessable_content
|
|
@@ -253,6 +257,7 @@ module Mbeditor
|
|
|
253
257
|
|
|
254
258
|
FileUtils.mkdir_p(File.dirname(new_path))
|
|
255
259
|
FileUtils.mv(old_path, new_path)
|
|
260
|
+
broadcast_files_changed
|
|
256
261
|
|
|
257
262
|
render json: {
|
|
258
263
|
ok: true,
|
|
@@ -274,9 +279,11 @@ module Mbeditor
|
|
|
274
279
|
|
|
275
280
|
if File.directory?(path)
|
|
276
281
|
FileUtils.rm_rf(path)
|
|
282
|
+
broadcast_files_changed
|
|
277
283
|
render json: { ok: true, type: "folder", path: relative_path(path) }
|
|
278
284
|
else
|
|
279
285
|
File.delete(path)
|
|
286
|
+
broadcast_files_changed
|
|
280
287
|
render json: { ok: true, type: "file", path: relative_path(path) }
|
|
281
288
|
end
|
|
282
289
|
rescue StandardError => e
|
|
@@ -312,23 +319,31 @@ module Mbeditor
|
|
|
312
319
|
render json: { error: e.message }, status: :unprocessable_content
|
|
313
320
|
end
|
|
314
321
|
|
|
315
|
-
# GET /mbeditor/search?q=...&offset=0&limit=50
|
|
322
|
+
# GET /mbeditor/search?q=...&offset=0&limit=50®ex=false&match_case=false&whole_word=false
|
|
316
323
|
def search
|
|
317
|
-
query
|
|
318
|
-
offset
|
|
319
|
-
limit
|
|
320
|
-
|
|
324
|
+
query = params[:q].to_s.strip
|
|
325
|
+
offset = [params[:offset].to_i, 0].max
|
|
326
|
+
limit = [[params[:limit].to_i > 0 ? params[:limit].to_i : 50, 200].min, 1].max
|
|
327
|
+
use_regex = params[:regex] == 'true'
|
|
328
|
+
match_case = params[:match_case] == 'true'
|
|
329
|
+
whole_word = params[:whole_word] == 'true'
|
|
330
|
+
needed = offset + limit + 1 # collect one extra to detect has_more
|
|
321
331
|
|
|
322
332
|
return render json: [] if query.blank?
|
|
323
333
|
return render json: { error: "Query too long" }, status: :bad_request if query.length > 500
|
|
324
334
|
|
|
325
335
|
# On first page, count total matches in parallel with fetching results.
|
|
326
|
-
count_thread = offset == 0 ? Thread.new { count_search_results(query) } : nil
|
|
336
|
+
count_thread = offset == 0 ? Thread.new { count_search_results(query, use_regex: use_regex, match_case: match_case, whole_word: whole_word) } : nil
|
|
327
337
|
|
|
328
|
-
results = stream_search_results(query, needed)
|
|
338
|
+
results = stream_search_results(query, needed, use_regex: use_regex, match_case: match_case, whole_word: whole_word)
|
|
329
339
|
has_more = results.length > offset + limit
|
|
330
340
|
response = { results: results[offset, limit] || [], has_more: has_more }
|
|
331
|
-
|
|
341
|
+
if count_thread
|
|
342
|
+
# Give the count thread up to 100 ms; omit total_count when it hasn't finished yet
|
|
343
|
+
# so the first page is never blocked by the counting subprocess.
|
|
344
|
+
count_thread.join(0.1)
|
|
345
|
+
response[:total_count] = count_thread.value unless count_thread.alive?
|
|
346
|
+
end
|
|
332
347
|
|
|
333
348
|
render json: response
|
|
334
349
|
rescue StandardError => e
|
|
@@ -661,6 +676,14 @@ module Mbeditor
|
|
|
661
676
|
|
|
662
677
|
private
|
|
663
678
|
|
|
679
|
+
def broadcast_files_changed
|
|
680
|
+
return unless defined?(ActionCable.server)
|
|
681
|
+
|
|
682
|
+
ActionCable.server.broadcast("mbeditor_editor", { type: "files_changed" })
|
|
683
|
+
rescue StandardError
|
|
684
|
+
# Never let a broadcast failure affect the HTTP response
|
|
685
|
+
end
|
|
686
|
+
|
|
664
687
|
def sanitize_branch_name(branch)
|
|
665
688
|
return nil if branch.blank?
|
|
666
689
|
str = branch.to_s.strip
|
|
@@ -696,11 +719,14 @@ module Mbeditor
|
|
|
696
719
|
# Stream search results using popen so we can stop reading early once we
|
|
697
720
|
# have collected `limit` matches (avoids buffering the entire rg/grep output
|
|
698
721
|
# in memory when searching large codebases for common tokens).
|
|
699
|
-
def stream_search_results(query, limit)
|
|
722
|
+
def stream_search_results(query, limit, use_regex: false, match_case: false, whole_word: false)
|
|
700
723
|
results = []
|
|
701
724
|
|
|
702
725
|
if RG_AVAILABLE
|
|
703
726
|
args = ["rg", "--json", "--no-ignore"]
|
|
727
|
+
args << "-F" unless use_regex
|
|
728
|
+
args << "--ignore-case" unless match_case
|
|
729
|
+
args << "--word-regexp" if whole_word
|
|
704
730
|
excluded_paths.each { |p| args << "--glob=!#{p}" }
|
|
705
731
|
args += ["--", query, workspace_root.to_s]
|
|
706
732
|
|
|
@@ -725,7 +751,10 @@ module Mbeditor
|
|
|
725
751
|
end
|
|
726
752
|
end
|
|
727
753
|
else
|
|
728
|
-
|
|
754
|
+
base_flags = use_regex ? "-E" : "-F"
|
|
755
|
+
args = ["grep", "-rn", base_flags]
|
|
756
|
+
args << "-i" unless match_case
|
|
757
|
+
args << "-w" if whole_word
|
|
729
758
|
excluded_dirnames.select { |d| d.match?(/\A[\w.\/-]+\z/) }.each { |d| args << "--exclude-dir=#{d}" }
|
|
730
759
|
args += [query, workspace_root.to_s]
|
|
731
760
|
|
|
@@ -756,17 +785,23 @@ module Mbeditor
|
|
|
756
785
|
|
|
757
786
|
# Count total matching lines across the workspace using rg --count (or grep -c).
|
|
758
787
|
# Fast: rg just counts without extracting context. Runs in a background thread.
|
|
759
|
-
def count_search_results(query)
|
|
788
|
+
def count_search_results(query, use_regex: false, match_case: false, whole_word: false)
|
|
760
789
|
total = 0
|
|
761
790
|
if RG_AVAILABLE
|
|
762
791
|
args = ["rg", "--count", "--no-ignore"]
|
|
792
|
+
args << "-F" unless use_regex
|
|
793
|
+
args << "--ignore-case" unless match_case
|
|
794
|
+
args << "--word-regexp" if whole_word
|
|
763
795
|
excluded_paths.each { |p| args << "--glob=!#{p}" }
|
|
764
796
|
args += ["--", query, workspace_root.to_s]
|
|
765
797
|
IO.popen(args, err: File::NULL) do |io|
|
|
766
798
|
io.each_line { |line| total += line.strip.split(":").last.to_i rescue 0 }
|
|
767
799
|
end
|
|
768
800
|
else
|
|
769
|
-
|
|
801
|
+
base_flags = use_regex ? "-E" : "-F"
|
|
802
|
+
args = ["grep", "-rc", base_flags]
|
|
803
|
+
args << "-i" unless match_case
|
|
804
|
+
args << "-w" if whole_word
|
|
770
805
|
excluded_dirnames.each { |d| args << "--exclude-dir=#{d}" }
|
|
771
806
|
args += [query, workspace_root.to_s]
|
|
772
807
|
IO.popen(args, err: File::NULL) do |io|
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
<%= stylesheet_link_tag "fontawesome.min", media: "all", preload_links_header: false %>
|
|
21
21
|
<%= stylesheet_link_tag "mbeditor/application", media: "all", preload_links_header: false %>
|
|
22
22
|
|
|
23
|
+
<!-- ── ActionCable (deferred — sets window.ActionCable for WebSocket service) ── -->
|
|
24
|
+
<% if defined?(ActionCable::Channel::Base) %>
|
|
25
|
+
<script defer src="<%= asset_path('actioncable.js') %>"></script>
|
|
26
|
+
<% end %>
|
|
23
27
|
<!-- ── Vendor JS (deferred — only needed inside Monaco callback) ── -->
|
|
24
28
|
<script defer src="<%= asset_path('react.min.js') %>"></script>
|
|
25
29
|
<script defer src="<%= asset_path('react-dom.min.js') %>"></script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbeditor
|
|
4
|
+
# Wraps the ActionCable logger and suppresses all log lines that mention
|
|
5
|
+
# Mbeditor channels so the development console stays readable.
|
|
6
|
+
# Non-Mbeditor ActionCable messages pass through unchanged.
|
|
7
|
+
class CableLogFilter < SimpleDelegator
|
|
8
|
+
SUPPRESS_PATTERN = /Mbeditor::|mbeditor_editor/
|
|
9
|
+
|
|
10
|
+
%w[debug info warn error fatal unknown].each do |level|
|
|
11
|
+
define_method(level) do |message = nil, &block|
|
|
12
|
+
msg = message.nil? && block ? block.call : message.to_s
|
|
13
|
+
return if msg.match?(SUPPRESS_PATTERN)
|
|
14
|
+
|
|
15
|
+
super(message, &block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Tagged-logging compat — the block body still passes through the filter.
|
|
20
|
+
def tagged(*tags, &block)
|
|
21
|
+
if __getobj__.respond_to?(:tagged)
|
|
22
|
+
__getobj__.tagged(*tags) { block.call }
|
|
23
|
+
else
|
|
24
|
+
block.call
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/mbeditor/engine.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "mbeditor/rack/silence_ping_request"
|
|
4
4
|
require "mbeditor/rack/handle_pending_migrations"
|
|
5
|
+
require "mbeditor/cable_log_filter"
|
|
5
6
|
|
|
6
7
|
module Mbeditor
|
|
7
8
|
class Engine < ::Rails::Engine
|
|
@@ -24,6 +25,15 @@ module Mbeditor
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
config.after_initialize do
|
|
28
|
+
# Silence ActionCable framework logs for Mbeditor channels (subscription
|
|
29
|
+
# confirmations, streaming notices, action invocations, disconnect messages).
|
|
30
|
+
# We wrap the existing ActionCable logger in a filter proxy rather than
|
|
31
|
+
# replacing it, so the host app's non-Mbeditor channel logs are unaffected.
|
|
32
|
+
if defined?(ActionCable)
|
|
33
|
+
original_logger = ActionCable.server.config.logger || Rails.logger
|
|
34
|
+
ActionCable.server.config.logger = Mbeditor::CableLogFilter.new(original_logger)
|
|
35
|
+
end
|
|
36
|
+
|
|
27
37
|
Mbeditor::RubyDefinitionService.cache_path =
|
|
28
38
|
Rails.root.join("tmp", "mbeditor_ruby_defs.json").to_s
|
|
29
39
|
|
|
@@ -17,7 +17,7 @@ module Mbeditor
|
|
|
17
17
|
path = normalized_request_path(env)
|
|
18
18
|
if root_request?(env, path)
|
|
19
19
|
@app.call(env)
|
|
20
|
-
elsif mbeditor_request?(path) || editor_asset_request?(env, path)
|
|
20
|
+
elsif mbeditor_request?(path) || cable_request?(path) || editor_asset_request?(env, path)
|
|
21
21
|
Rails.logger.silence { @app.call(env) }
|
|
22
22
|
else
|
|
23
23
|
@app.call(env)
|
|
@@ -30,6 +30,9 @@ module Mbeditor
|
|
|
30
30
|
path.start_with?("/mbeditor/")
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def cable_request?(path)
|
|
34
|
+
path == "/cable" || path.start_with?("/cable/")
|
|
35
|
+
end
|
|
33
36
|
# Silence asset pipeline requests that belong to the editor:
|
|
34
37
|
# - /assets/mbeditor/... is always an editor asset (CSS/JS bundle)
|
|
35
38
|
# - other /assets/... requests are silenced only when the browser is
|
data/lib/mbeditor/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.4.2
|
|
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-04-
|
|
11
|
+
date: 2026-04-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -78,9 +78,11 @@ files:
|
|
|
78
78
|
- app/assets/javascripts/mbeditor/git_service.js
|
|
79
79
|
- app/assets/javascripts/mbeditor/search_service.js
|
|
80
80
|
- app/assets/javascripts/mbeditor/tab_manager.js
|
|
81
|
+
- app/assets/javascripts/mbeditor/websocket_service.js
|
|
81
82
|
- app/assets/stylesheets/mbeditor/application.css
|
|
82
83
|
- app/assets/stylesheets/mbeditor/editor.css
|
|
83
84
|
- app/assets/stylesheets/mbeditor/themes.css
|
|
85
|
+
- app/channels/mbeditor/editor_channel.rb
|
|
84
86
|
- app/controllers/mbeditor/application_controller.rb
|
|
85
87
|
- app/controllers/mbeditor/editors_controller.rb
|
|
86
88
|
- app/controllers/mbeditor/git_controller.rb
|
|
@@ -98,6 +100,7 @@ files:
|
|
|
98
100
|
- config/initializers/assets.rb
|
|
99
101
|
- config/routes.rb
|
|
100
102
|
- lib/mbeditor.rb
|
|
103
|
+
- lib/mbeditor/cable_log_filter.rb
|
|
101
104
|
- lib/mbeditor/configuration.rb
|
|
102
105
|
- lib/mbeditor/engine.rb
|
|
103
106
|
- lib/mbeditor/rack/handle_pending_migrations.rb
|