nrepl-lazuli 0.6.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7d68153dc3d474b045d5856d097c51670cfeb3ae688396a45c5cd16d117232a
4
- data.tar.gz: 5e65794b9839a3f7d9c0aa341f3675c58e1a25290afa0f57f532cb1cb23a8be2
3
+ metadata.gz: 850adfc6733e86d502ffd88241f4dff665e34870c6c2a7d6a436ee77685565e8
4
+ data.tar.gz: 50d6783deae1558218fc67a1d47590c2e92d07f964daa7038d3b6f51aec2d7dc
5
5
  SHA512:
6
- metadata.gz: 14c80200837368c174425e48735927e8c4e0be8214bbd5c1171e1338f19ac3b225b02a0631c9a269f19bc5846d7eb540944a96edf75ffd922cf8938923803f4a
7
- data.tar.gz: e66a6400f737902c281c0c2c09c0d584ebbc5bd47a8d1262fd3b46db6afdbd32a57d1a5acfc220baa37bdf2fa9c02425d6f4f76acff4f16ecc7fca85bc5e910a
6
+ metadata.gz: 6133a4d27d93dcaf60bb06c4415384cb09e31bf08a89dd03059181fec2eba3ebc7695a890a77f8349fdea91eda292d2e8444ae365e8d59a00f96b41dec435bd8
7
+ data.tar.gz: a3df651980e3684fd0fffd2ef2a400dfdcf25b79ffc3e554c8425e9a880d929effc0b9fb62631f5ea8a6cb84c6ec72e80b07b7c7b3ea850372fc8f85b0239d12
@@ -10,7 +10,7 @@ class BEncode
10
10
  when Hash
11
11
  "d#{data.map { |k, v| encode(k) + encode(v) }.join}e"
12
12
  else
13
- raise ArgumentError, "Cannot BEncode type: #{data.class}"
13
+ raise ArgumentError, "Cannot BEncode type: #{data.class} on text #{data.inspect}"
14
14
  end
15
15
  end
16
16
 
@@ -43,6 +43,10 @@ module NREPL
43
43
  case msg['op']
44
44
  when 'clone'
45
45
  register_session(msg)
46
+ when 'complete'
47
+ get_completions(msg)
48
+ when 'find_definition'
49
+ find_definition(msg)
46
50
  when 'describe'
47
51
  describe_msg(msg)
48
52
  when 'eval'
@@ -152,6 +156,97 @@ module NREPL
152
156
  end
153
157
  end
154
158
 
159
+ private def get_completions(msg)
160
+ wrap_all = ->(coll, type) do
161
+ coll.lazy.map { |c| { 'candidate' => c.to_s, 'type' => type } }
162
+ end
163
+ prefix_reg = Regexp.new(msg['prefix'].split('').map { |x| Regexp.escape(x) }.join('.*'))
164
+ context = msg.fetch('context', {})
165
+ methods = ->(callee) do
166
+ wrap_all.(callee.public_methods, 'method/public') +
167
+ wrap_all.(callee.protected_methods, 'method/protected') +
168
+ wrap_all.(callee.private_methods, 'method/private')
169
+ end
170
+
171
+ completions = case context['type']
172
+ when 'symbol'
173
+ prefix_reg = Regexp.new(msg['prefix'].sub(':', ''))
174
+ symbols = Symbol.all_symbols.lazy.select { |s| s.to_s =~ prefix_reg }
175
+ completions = symbols.map { |c| { 'candidate' => ":#{c}", 'type' => 'label' } } +
176
+ symbols.map { |c| { 'candidate' => "#{c}:", 'type' => 'label' } }
177
+ send_msg(response_for(msg, {
178
+ 'status' => ['done'],
179
+ 'completions' => completions.to_a.uniq
180
+ }))
181
+ return
182
+ when 'constant'
183
+ me = evaluate_code("self", msg)
184
+ wrap_all.(me.class.constants, 'constant') +
185
+ wrap_all.(Object.constants, 'constant')
186
+ when 'call'
187
+ if context['separator'] == '::'
188
+ wrap_all.(evaluate_code(context['callee'], msg).constants, 'constant')
189
+ else
190
+ methods.(evaluate_code(context['callee'], msg))
191
+ end
192
+ when 'identifier'
193
+ methods.(evaluate_code('self', msg)) +
194
+ wrap_all.(evaluate_code('binding.local_variables', msg), 'local')
195
+ when 'instance_variable'
196
+ wrap_all.(evaluate_code('instance_variables', msg), 'attribute/instance')
197
+ when 'class_variable'
198
+ me = evaluate_code('self', msg)
199
+ if me.instance_of?(Class)
200
+ wrap_all.(me.class_variables, 'attribute/class')
201
+ else
202
+ wrap_all.(me.class.class_variables, 'attribute/class')
203
+ end
204
+ when 'scope_resolution'
205
+ wrap_all.(evaluate_code(context['callee'], msg).constants, 'constant')
206
+ end
207
+
208
+ send_msg(response_for(msg, {
209
+ 'status' => ['done'],
210
+ 'completions' => completions.select { |x| x['candidate'] =~ prefix_reg }.to_a.uniq
211
+ }))
212
+ rescue Exception
213
+ send_msg(response_for(msg, {
214
+ 'status' => ['done'],
215
+ 'completions' => []
216
+ }))
217
+ end
218
+
219
+ private def find_definition(msg)
220
+ # {
221
+ # "op" => "find_definition",
222
+ # "file" => "/tmp/some_file.rb",
223
+ # "line" => 7,
224
+ # "symbol" => "upcase",
225
+ # "id" => "d1",
226
+ # "context" => {"prefix" => "a_string"}
227
+ # }
228
+ prefix = msg.fetch('context', {}).fetch('prefix', 'self')
229
+ prefix_class = evaluate_code("#{prefix}.class", msg)
230
+ result = prefix_class.__lazuli_source_location(msg.fetch('symbol').intern)
231
+
232
+ if result.nil?
233
+ send_msg(response_for(msg, {
234
+ 'status' => ['done', 'notfound']
235
+ }))
236
+ else
237
+ file, row = result
238
+ send_msg(response_for(msg, {
239
+ 'file' => file,
240
+ 'line' => row - 1,
241
+ 'status' => ['done']
242
+ }))
243
+ end
244
+ rescue Exception => e
245
+ send_msg(response_for(msg, {
246
+ 'status' => ['done', 'notfound']
247
+ }))
248
+ end
249
+
155
250
  private def get_watches(msg)
156
251
  msg['id'] ||= "eval_#{@counter += 1}"
157
252
  file = msg['file']
@@ -213,15 +308,13 @@ module NREPL
213
308
  code = code.gsub(/^NREPL\.stop!$/, "NREPL.stop_#{method_name}(binding)")
214
309
  define_stop_function!(msg, method_name)
215
310
  end
216
-
217
- original_bind = if msg['stop_id']
218
- @pending_evals.fetch(msg['stop_id'], {})[:binding]
219
- elsif msg['watch_id']
220
- @bindings_by_id.fetch(msg['watch_id'], {})[:binding]
221
- else
222
- find_row_based_binding(msg) || @binding
223
- end
224
- evaluate_code(code, msg['file'], msg['line'], original_bind)
311
+ begin
312
+ res = evaluate_code(code, msg)
313
+ [:result, res]
314
+ rescue Exception => e
315
+ raise e if e.is_a?(SyntaxError)
316
+ [:error, e.inspect.sub(/\>$/, ' stack=' + e.backtrace.inspect+'>')]
317
+ end.inspect
225
318
  end
226
319
 
227
320
  unless pending_eval[:stopped?]
@@ -229,10 +322,77 @@ module NREPL
229
322
  end
230
323
  end
231
324
 
232
- private def evaluate_code(code, file, line, bind)
325
+ private def inspect_result(result)
326
+ return result.inspect
327
+
328
+ class_for_res = ->{ result.class.ancestors.find(&:name) }
329
+ case result
330
+ # when BigDecimal
331
+ # ["literal", "BigDecimal: #{result.to_f}"]
332
+ when String
333
+ ["string", result]
334
+ when Numeric
335
+ ["number", result.to_s]
336
+ when Hash
337
+ [
338
+ "map",
339
+ nil,
340
+ "{",
341
+ " => ",
342
+ ", ",
343
+ "}",
344
+ result.map { |(k, v)| [inspect_result(k), inspect_result(v)] }
345
+ ]
346
+ when Array
347
+ ["coll", nil, "[", ", ", "]", result.map { |i| inspect_result(i) }]
348
+ # when Enumerable
349
+ # ["coll", class_for_res.().name, "[", ", ", "]", result.map { |i| inspect_result(i) }]
350
+ when Exception
351
+ [
352
+ "exception",
353
+ ["literal", "#{class_for_res.().name}: #{result.message}"],
354
+ (result.backtrace_locations || []).map { |l| [l.path, l.lineno, nil, l.label] }
355
+ ]
356
+ else
357
+ inspected = result.inspect
358
+ match = inspected.match(/#\<(.*)\>/)
359
+ if match
360
+ vars = match.captures[0]
361
+ inner_vars = vars.scan(/\w(?:[\w\d_]\??)+=/)
362
+ inners = inner_vars.flat_map do |v|
363
+ begin
364
+ v = v.chop
365
+ [[["literal", v], inspect_result(result.send(v))]]
366
+ rescue NoMethodError
367
+ []
368
+ end
369
+ end
370
+ [
371
+ "object",
372
+ class_for_res.().name,
373
+ result.inspect,
374
+ inners
375
+ ]
376
+ else
377
+ [ "literal", result.inspect ]
378
+ end
379
+ end
380
+ end
381
+
382
+ private def evaluate_code(code, msg)
383
+ bind = if msg['stop_id']
384
+ @pending_evals.fetch(msg['stop_id'], {})[:binding]
385
+ elsif msg['watch_id']
386
+ @bindings_by_id.fetch(msg['watch_id'], {})[:binding]
387
+ else
388
+ find_row_based_binding(msg) || @binding
389
+ end
390
+ file = msg['file'] || 'EVAL'
391
+ line = (msg['line'] || 0) + 1
392
+ # evaluate_code(code, msg['file'], msg['line'], original_bind)
233
393
  bind ||= TOPLEVEL_BINDING.dup
234
- line = line ? line + 1 : 1
235
- eval(code, bind, file || "EVAL", line).inspect
394
+ # line = line ? line + 1 : 1
395
+ eval(code, bind, file, line)
236
396
  end
237
397
 
238
398
  private def find_row_based_binding(msg)
@@ -302,7 +462,8 @@ module NREPL
302
462
  end
303
463
 
304
464
  def send_exception(msg, e)
305
- send_msg(response_for(msg, { 'ex' => e.message, 'status' => ['done', 'error'] }))
465
+ error = e.inspect.sub(/\>$/, ' stack=' + e.backtrace.inspect+'>')
466
+ send_msg(response_for(msg, { 'ex' => error, 'status' => ['done', 'error'] }))
306
467
  end
307
468
 
308
469
  def send_msg(msg)
@@ -8,6 +8,8 @@ require_relative 'bencode'
8
8
  require_relative 'connection'
9
9
  require_relative 'fake_stdout'
10
10
 
11
+ $pwd = Dir.pwd
12
+
11
13
  module NREPL
12
14
  class Server
13
15
  attr_reader :debug, :port, :host, :call_trace
@@ -39,7 +41,7 @@ module NREPL
39
41
  loader: nil
40
42
  )
41
43
  @port = port
42
- @pwd = pwd
44
+ $pwd = pwd
43
45
  @host = host
44
46
  @debug = debug
45
47
  @connections = Set.new
@@ -107,11 +109,11 @@ module NREPL
107
109
  end
108
110
 
109
111
  def auto_create_bindings!
110
- dir_regex = Regexp.new("^#{Regexp.escape(@pwd)}")
112
+ dir_regex = Regexp.new("^#{Regexp.escape($pwd)}")
111
113
  trace_proc = proc do |tp|
112
114
  path = tp.path
113
115
  next if tp.path =~ /^(\<|.eval)/
114
- path = File.join(@pwd, path) if File.dirname(path) == '.'
116
+ path = File.join($pwd, path) if File.dirname(path) == '.'
115
117
  id = "#{path}:#{tp.lineno}"
116
118
  b = tp.binding
117
119
  @bindings_by_id[id] = {binding: b, file: path, row: tp.lineno-1}
@@ -182,13 +184,6 @@ module NREPL
182
184
 
183
185
  Thread.prepend(ThreadPatch)
184
186
 
185
- # Also...
186
- module MethodLocationFixer
187
- def __lazuli_source_location
188
- @__lazuli_source_location || source_location
189
- end
190
- end
191
-
192
187
  module DefinitionFixer
193
188
  @@definitions = {}
194
189
 
@@ -211,8 +206,8 @@ module NREPL
211
206
 
212
207
  def method_added(method_name)
213
208
  return if method_name == :__lazuli_source_location
214
- pwd = Dir.pwd
215
- path = caller.select { |x| x.start_with?(pwd) }[0]
209
+
210
+ path = caller.select { |x| x.start_with?($pwd) }[0]
216
211
  if path
217
212
  (file, row) = path.split(/:/)
218
213
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nrepl-lazuli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maurício Szabo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: A Ruby nREPL server, made to be used with Lazuli plug-in (but can be
13
13
  used with any nREPL client too)
@@ -41,7 +41,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0'
43
43
  requirements: []
44
- rubygems_version: 3.6.2
44
+ rubygems_version: 4.0.6
45
45
  specification_version: 4
46
46
  summary: A Ruby nREPL server
47
47
  test_files: []