mbeditor 0.12.8 → 0.13.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +207 -0
  3. data/app/assets/javascripts/mbeditor/application.js +7 -1
  4. data/app/assets/javascripts/mbeditor/collaboration_service.js +31 -0
  5. data/app/assets/javascripts/mbeditor/color_provider.js +5 -0
  6. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +3 -1
  7. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +135 -40
  8. data/app/assets/javascripts/mbeditor/components/FileTree.js +6 -1
  9. data/app/assets/javascripts/mbeditor/components/GitPanel.js +10 -1
  10. data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +15 -8
  11. data/app/assets/javascripts/mbeditor/components/ImportDialog.js +216 -0
  12. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +629 -137
  13. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +25 -6
  14. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +218 -13
  15. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +39 -33
  16. data/app/assets/javascripts/mbeditor/components/TabBar.js +11 -2
  17. data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +312 -0
  18. data/app/assets/javascripts/mbeditor/editor_plugins.js +444 -116
  19. data/app/assets/javascripts/mbeditor/file_import.js +78 -0
  20. data/app/assets/javascripts/mbeditor/file_service.js +122 -18
  21. data/app/assets/javascripts/mbeditor/git_service.js +130 -8
  22. data/app/assets/javascripts/mbeditor/history_service.js +33 -38
  23. data/app/assets/javascripts/mbeditor/log_service.js +4 -0
  24. data/app/assets/javascripts/mbeditor/search_service.js +30 -2
  25. data/app/assets/javascripts/mbeditor/tab_manager.js +59 -6
  26. data/app/assets/javascripts/mbeditor/websocket_service.js +88 -4
  27. data/app/assets/stylesheets/mbeditor/editor.css +302 -55
  28. data/app/channels/mbeditor/channel_authentication.rb +7 -0
  29. data/app/channels/mbeditor/editor_channel.rb +6 -3
  30. data/app/controllers/mbeditor/application_controller.rb +5 -0
  31. data/app/controllers/mbeditor/editors_controller.rb +173 -29
  32. data/app/controllers/mbeditor/git_controller.rb +3 -3
  33. data/app/controllers/mbeditor/logs_controller.rb +3 -1
  34. data/app/services/mbeditor/collaboration_doc_store.rb +7 -0
  35. data/app/services/mbeditor/editor_state_service.rb +36 -26
  36. data/app/services/mbeditor/exclusion_matcher.rb +12 -10
  37. data/app/services/mbeditor/file_operation_service.rb +38 -3
  38. data/app/services/mbeditor/file_tree_service.rb +30 -2
  39. data/app/services/mbeditor/git_combined_diff_service.rb +13 -3
  40. data/app/services/mbeditor/git_commit_detail_service.rb +20 -16
  41. data/app/services/mbeditor/git_diff_service.rb +5 -1
  42. data/app/services/mbeditor/git_info_service.rb +20 -8
  43. data/app/services/mbeditor/git_line_diff_service.rb +6 -2
  44. data/app/services/mbeditor/git_service.rb +65 -15
  45. data/app/services/mbeditor/js_definition_service.rb +3 -1
  46. data/app/services/mbeditor/js_globals_service.rb +7 -4
  47. data/app/services/mbeditor/js_members_service.rb +3 -2
  48. data/app/services/mbeditor/js_program_service.rb +15 -5
  49. data/app/services/mbeditor/js_syntax_check_service.rb +15 -4
  50. data/app/services/mbeditor/process_runner.rb +42 -12
  51. data/app/services/mbeditor/ri_definition_service.rb +8 -1
  52. data/app/services/mbeditor/route_service.rb +45 -8
  53. data/app/services/mbeditor/rubocop_run_service.rb +86 -0
  54. data/app/services/mbeditor/ruby_definition_service.rb +23 -4
  55. data/app/services/mbeditor/schema_service.rb +65 -64
  56. data/app/services/mbeditor/search_replace_service.rb +75 -10
  57. data/app/services/mbeditor/test_runner_service.rb +98 -10
  58. data/lib/mbeditor/cable_log_filter.rb +8 -2
  59. data/lib/mbeditor/configuration.rb +12 -1
  60. data/lib/mbeditor/editor_bootstrap.rb +18 -12
  61. data/lib/mbeditor/engine.rb +22 -3
  62. data/lib/mbeditor/exception_log.rb +2 -2
  63. data/lib/mbeditor/pending_migrations.rb +33 -0
  64. data/lib/mbeditor/rack/handle_pending_migrations.rb +25 -15
  65. data/lib/mbeditor/rack/pending_migration_bypass.rb +104 -0
  66. data/lib/mbeditor/rack/silence_ping_request.rb +10 -2
  67. data/lib/mbeditor/route_map.rb +2 -0
  68. data/lib/mbeditor/ruby_lsp_client.rb +71 -12
  69. data/lib/mbeditor/version.rb +1 -1
  70. metadata +7 -2
@@ -5,6 +5,7 @@ require "json"
5
5
  require "digest"
6
6
  require "shellwords"
7
7
  require "timeout"
8
+ require "uri"
8
9
 
9
10
  module Mbeditor
10
11
  # Manages a persistent ruby-lsp process per workspace and speaks the
@@ -88,10 +89,13 @@ module Mbeditor
88
89
 
89
90
  # A snapshot for the editor's status indicator. Deliberately does not start
90
91
  # the process — asking "how are you?" must not be what boots the server.
92
+ #
93
+ # Deliberately lock-free: @state_mutex is held for the whole of a start
94
+ # (up to INIT_TIMEOUT) and a restart, and a status chip polled every 10s
95
+ # must not queue behind a handshake. Each field is one reference read, so
96
+ # the worst case is a snapshot that is one transition out of date.
91
97
  def health
92
- @state_mutex.synchronize do
93
- { state: @state, restarts: @crash_times.length, error: @last_error }
94
- end
98
+ { state: @state, restarts: @crash_times.length, error: @last_error }
95
99
  end
96
100
 
97
101
  # Clears the crash budget so a client latched at :failed can be revived
@@ -109,22 +113,37 @@ module Mbeditor
109
113
  end
110
114
 
111
115
  # Syncs the document (didOpen / full-text didChange) and issues a request
112
- # against it under one mutex, so concurrent Puma threads can't interleave
113
- # a positional request with a stale document.
116
+ # against it, so concurrent Puma threads can't interleave a positional
117
+ # request with a stale document.
114
118
  # +params+ is an explicit hash (not keywords) so it can't collide with the
115
119
  # +timeout:+ keyword.
120
+ #
121
+ # @doc_mutex covers the sync and the write, not the wait: LSP processes
122
+ # messages in order, so once the request is on the wire behind its didOpen
123
+ # the server already sees the right document. Holding it across the
124
+ # round-trip serialised every ruby-lsp request in the process for up to the
125
+ # full request timeout.
116
126
  def request_with_document(method, path, content, params = {}, timeout: nil)
117
127
  raise NotReadyError, "ruby-lsp is not running" unless ready?
118
128
 
119
- uri = "file://#{path}"
120
- @doc_mutex.synchronize do
129
+ uri = file_uri(path)
130
+ timeout ||= (Mbeditor.configuration.ruby_lsp_timeout || 3).to_f
131
+ id, queue = @doc_mutex.synchronize do
121
132
  sync_document(uri, content)
122
- request(method, params.merge(textDocument: { uri: uri }), timeout: timeout)
133
+ send_request(method, params.merge(textDocument: { uri: uri }))
123
134
  end
135
+ await_response(method, id, queue, timeout)
124
136
  end
125
137
 
126
138
  def request(method, params, timeout: nil)
127
139
  timeout ||= (Mbeditor.configuration.ruby_lsp_timeout || 3).to_f
140
+ id, queue = send_request(method, params)
141
+ await_response(method, id, queue, timeout)
142
+ end
143
+
144
+ # Registers a response queue and puts the request on the wire. Returns
145
+ # [id, queue] for #await_response.
146
+ def send_request(method, params)
128
147
  queue = Queue.new
129
148
  id = @pending_mutex.synchronize do
130
149
  @next_id += 1
@@ -132,15 +151,29 @@ module Mbeditor
132
151
  @next_id
133
152
  end
134
153
 
135
- write_message({ jsonrpc: "2.0", id: id, method: method, params: params })
154
+ begin
155
+ write_message({ jsonrpc: "2.0", id: id, method: method, params: params })
156
+ rescue StandardError
157
+ @pending_mutex.synchronize { @pending.delete(id) }
158
+ raise
159
+ end
160
+
161
+ [id, queue]
162
+ end
136
163
 
164
+ def await_response(method, id, queue, timeout)
137
165
  msg = pop_with_timeout(queue, timeout)
138
- raise TimeoutError, "#{method} timed out after #{timeout}s" if msg.nil?
166
+ if msg.nil?
167
+ # Nobody is going to read this answer; tell the server to stop
168
+ # computing it rather than leave it indexing for an abandoned caller.
169
+ cancel_request(id)
170
+ raise TimeoutError, "#{method} timed out after #{timeout}s"
171
+ end
139
172
  raise Error, msg["error"]["message"].to_s if msg["error"]
140
173
 
141
174
  msg["result"]
142
175
  ensure
143
- @pending_mutex.synchronize { @pending.delete(id) } if id
176
+ @pending_mutex.synchronize { @pending.delete(id) }
144
177
  end
145
178
 
146
179
  # Queue#pop only accepts a timeout: keyword from Ruby 3.2 onwards, and the
@@ -187,6 +220,24 @@ module Mbeditor
187
220
 
188
221
  private
189
222
 
223
+ # Ruby 3.4 renamed the RFC2396 parser and deprecated escape on the RFC3986
224
+ # one DEFAULT_PARSER now points at, so name it explicitly where it exists.
225
+ URI_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
226
+ private_constant :URI_PARSER
227
+
228
+ # A workspace path is not a URI: a space raises URI::InvalidURIError inside
229
+ # ruby-lsp and a `#` silently truncates the path at the fragment. Every
230
+ # file:// URI this class builds goes through here.
231
+ def file_uri(path)
232
+ "file://#{URI_PARSER.escape(path.to_s)}"
233
+ end
234
+
235
+ def cancel_request(id)
236
+ write_message({ jsonrpc: "2.0", method: "$/cancelRequest", params: { id: id } })
237
+ rescue StandardError
238
+ nil
239
+ end
240
+
190
241
  def ensure_started
191
242
  @state_mutex.synchronize do
192
243
  return if @state == :ready || @state == :failed
@@ -221,7 +272,7 @@ module Mbeditor
221
272
 
222
273
  result = request("initialize", {
223
274
  processId: Process.pid,
224
- rootUri: "file://#{@root}",
275
+ rootUri: file_uri(@root),
225
276
  capabilities: {
226
277
  textDocument: {
227
278
  synchronization: { didSave: false },
@@ -365,6 +416,14 @@ module Mbeditor
365
416
  end
366
417
 
367
418
  def cleanup_process
419
+ # A failed handshake (INIT_TIMEOUT on a large app) otherwise orphans a
420
+ # ruby-lsp that is still indexing. No-op once the child has exited.
421
+ begin
422
+ Process.kill("-KILL", @wait_thr.pid) if @wait_thr
423
+ rescue StandardError
424
+ nil
425
+ end
426
+
368
427
  [@stdin, @stdout, @stderr].each do |io|
369
428
  begin
370
429
  io&.close
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbeditor
4
- VERSION = "0.12.8"
4
+ VERSION = "0.13.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.12.8
4
+ version: 0.13.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-08-07 00:00:00.000000000 Z
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -71,6 +71,7 @@ files:
71
71
  - app/assets/javascripts/mbeditor/components/FileTree.js
72
72
  - app/assets/javascripts/mbeditor/components/GitPanel.js
73
73
  - app/assets/javascripts/mbeditor/components/ImportConflictModal.js
74
+ - app/assets/javascripts/mbeditor/components/ImportDialog.js
74
75
  - app/assets/javascripts/mbeditor/components/LogPanel.js
75
76
  - app/assets/javascripts/mbeditor/components/MbeditorApp.js
76
77
  - app/assets/javascripts/mbeditor/components/ModelGraph.js
@@ -79,6 +80,7 @@ files:
79
80
  - app/assets/javascripts/mbeditor/components/ShortcutHelp.js
80
81
  - app/assets/javascripts/mbeditor/components/TabBar.js
81
82
  - app/assets/javascripts/mbeditor/components/TestResultsPanel.js
83
+ - app/assets/javascripts/mbeditor/components/TestRunPanel.js
82
84
  - app/assets/javascripts/mbeditor/conflict_parser.js
83
85
  - app/assets/javascripts/mbeditor/editor_plugins.js
84
86
  - app/assets/javascripts/mbeditor/editor_store.js
@@ -134,6 +136,7 @@ files:
134
136
  - app/services/mbeditor/redmine_service.rb
135
137
  - app/services/mbeditor/ri_definition_service.rb
136
138
  - app/services/mbeditor/route_service.rb
139
+ - app/services/mbeditor/rubocop_run_service.rb
137
140
  - app/services/mbeditor/ruby_definition_service.rb
138
141
  - app/services/mbeditor/safe_path.rb
139
142
  - app/services/mbeditor/schema_service.rb
@@ -151,8 +154,10 @@ files:
151
154
  - lib/mbeditor/engine.rb
152
155
  - lib/mbeditor/exception_log.rb
153
156
  - lib/mbeditor/mount_path.rb
157
+ - lib/mbeditor/pending_migrations.rb
154
158
  - lib/mbeditor/private_routes.rb
155
159
  - lib/mbeditor/rack/handle_pending_migrations.rb
160
+ - lib/mbeditor/rack/pending_migration_bypass.rb
156
161
  - lib/mbeditor/rack/resilient_router.rb
157
162
  - lib/mbeditor/rack/silence_ping_request.rb
158
163
  - lib/mbeditor/route_map.rb