rider-server 0.1.0 → 0.1.1

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: 302928be71e4f0608534e86f87c555a0f3507004c93c058947b74d480cfde558
4
- data.tar.gz: 80707ddff979bb2a92f7f696dcbc44ac68830d56dffa11cfc021aa5eec06d743
3
+ metadata.gz: acb6833a29ab9facb82c72d8f2e012ab0dd7a44dbd1ad93dfe69f8d1bff2a765
4
+ data.tar.gz: 62834c0a615baa860e1e7650f14353180b874cf877833cc94b6f9804edd32efa
5
5
  SHA512:
6
- metadata.gz: f9fcc56b8cbceaa6df781155af166873e66c0bfe1261041b6667759dfc5eb56c3c9dfd6943b45e1e7f302cb0737e21da048ba399f837dec4a77416cb7af037bf
7
- data.tar.gz: 74527b4e988b97b6ed527530bbdef94cc6b48776c74fef12930dbf70a59c648209f3afa615ebb835861acba0b0eba70884107268b65e7aa12da0adb7b63040f8
6
+ metadata.gz: bec9b6906b7e47954bdf9a85fcb3a47ca89d86f63f68023b90d0c8ac83028f6aa7681ad0a4042934cd4a663a0f86f344d8ee11374816efe5616d5eaeaa2a8b75
7
+ data.tar.gz: a0d1b287bfeeaf68bbd82c9d72d7bb9405a9873286993e6171cd5f754c545c56cdcde1f186e8c9edb755a1e883a627ce6b33eeea2b21a047220ced97e3e973e5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.1] - 2024-07-31
4
+
5
+ - Basic completion
6
+ - Basic symbol lookup
7
+ - Fix ancestor lookup
8
+
3
9
  ## [0.1.0] - 2024-07-27
4
10
 
5
11
  - Initial release
data/COPYING ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2024 Russell Sim <rsl@simopolis.xyz>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ “Software”), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -32,10 +32,12 @@ dependencies. Then, run `rake test` to run the tests. You can also run
32
32
  experiment.
33
33
 
34
34
  To install this gem onto your local machine, run `bundle exec rake
35
- install`. To release a new version, update the version number in
36
- `version.rb`, and then run `bundle exec rake release`, which will
37
- create a git tag for the version, push git commits and the created
38
- tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+ install`.
36
+
37
+ To release a new version run `rake bump:patch` and then run `bundle
38
+ exec rake release`, which will create a git tag for the version, push
39
+ git commits and the created tag, and push the `.gem` file to
40
+ [rubygems.org](https://rubygems.org).
39
41
 
40
42
  ## Contributing
41
43
 
@@ -11,14 +11,13 @@ require "rider_server/utils"
11
11
 
12
12
  module RiderServer
13
13
  module Inspect
14
-
15
14
  def self.safely_eval(string, file, line)
16
15
  code = <<~HEREDOC
17
- begin
18
- #{string}
19
- rescue StandardError => e
20
- e
21
- end
16
+ begin
17
+ #{string}
18
+ rescue StandardError => e
19
+ e
20
+ end
22
21
  HEREDOC
23
22
  eval(code, TOPLEVEL_BINDING, file, line) # rubocop:disable Security/Eval
24
23
  end
@@ -34,11 +33,8 @@ module RiderServer
34
33
  end
35
34
 
36
35
  def self.ancestors(obj)
37
- # Look for instance ancestors on the class.
38
- obj = obj.class if !obj.respond_to?(:ancestors) && obj.class.respond_to?(:ancestors)
39
-
40
- if obj.class.respond_to?(:ancestors)
41
- obj.class.ancestors.map do |item|
36
+ if obj.respond_to?(:ancestors)
37
+ obj.ancestors.map do |item|
42
38
  {
43
39
  "name" => item.to_s,
44
40
  "value" => Utils.rider_inspect(item),
@@ -104,14 +104,14 @@ module RiderServer
104
104
  response.set("out", e.full_message)
105
105
  response.status("eval-error", "done")
106
106
  if session
107
- exception_id = session.add_exception(operation["id"], e)
108
- response.set("rider/exception-id", exception_id)
107
+ exception = session.push_exception(operation["id"], e)
108
+ response.set("rider/exception-id", exception["id"])
109
109
  end
110
-
111
- # Broadcast the exception to sessions that are listening
112
- @sessions_catching_exceptions.each do |s|
113
- next if session == s
114
- @sessions[s]&.add_exception(operation["id"], e)
110
+ @sessions_catching_exceptions.each do |session_id|
111
+ session = @sessions[session_id]
112
+ next if session.nil?
113
+ exception = session.push_exception(operation["id"], e)
114
+ response.set("rider/exception-id", exception["id"])
115
115
  end
116
116
  send_response(response)
117
117
  end
@@ -9,6 +9,7 @@ module RiderServer
9
9
 
10
10
  argument :id, :string, "The request id", required: true
11
11
  argument :prefix, :string, "The prefix to complete", required: true
12
+ argument :complete_fn, :string, "What completion function to use, top-level, module, method or singleton-method"
12
13
  argument :ns, :string, "The namespace to search for completions"
13
14
 
14
15
  def initialize(*args)
@@ -20,38 +21,40 @@ module RiderServer
20
21
  response = Response.new(operation)
21
22
 
22
23
  prefix = operation["prefix"]
23
- log.info "prefix: #{prefix}"
24
24
  ns = operation["ns"]
25
- # options = operation["options"]
25
+ complete_fn = operation.fetch("complete-fn", "top-level")
26
26
 
27
- log.info "prefix: #{prefix}"
28
- ns_object = @workspace.lookup_module(ns)
29
- log.info "ns_object: #{ns_object}"
30
-
31
- response.set("completions", lookup_module(prefix, ns_object))
32
- log.info "completions: #{response["completions"]}"
27
+ if prefix.nil? || parse_string(prefix).nil?
28
+ response.set("completions", [])
29
+ else
30
+ ns_object = if ns.empty?
31
+ ::Object
32
+ else
33
+ @workspace.lookup_module(ns)
34
+ end
33
35
 
36
+ # If the prefix isn't parseable, return an empty list
37
+ response.set("completions", lookup_module(prefix, klass: ns_object, completion_fn: complete_fn))
38
+ end
34
39
  response.status("done")
35
40
  response
36
- rescue ScriptError, StandardError => e
37
- log.error "Error in completions: #{e}"
38
- log.error e.backtrace.join("\n")
39
- raise e
40
41
  end
41
42
 
42
- private
43
-
44
- def lookup_module(module_name, klass = ::Object)
43
+ def lookup_module(module_name, klass: ::Object, completion_fn: "top-level")
45
44
  node = parse_string(module_name)
46
45
  case node.type
47
46
  when :const
48
47
  ns = lookup_module_ast(node.children.first, klass)
49
48
  prefix = node.children.last
50
- all_constants(ns, prefix) + all_methods(ns, prefix)
49
+ all_constants(ns, prefix)
51
50
  when :send
52
51
  ns = lookup_module_ast(node.children.first, klass)
53
52
  prefix = node.children.last
54
- all_methods(ns, prefix)
53
+ if completion_fn == "method"
54
+ all_constants(ns, prefix) + encode_methods(class_instance_methods(ns, prefix) + class_singleton_methods(::Kernel, prefix))
55
+ else
56
+ all_constants(ns, prefix) + encode_methods(class_methods(ns, prefix) + class_singleton_methods(::Kernel, prefix))
57
+ end
55
58
  else
56
59
  []
57
60
  end
@@ -64,6 +67,8 @@ module RiderServer
64
67
  end
65
68
 
66
69
  def lookup_module_ast(node, klass = ::Object)
70
+ return klass if node.nil?
71
+
67
72
  case node.type
68
73
  when :cbase
69
74
  ::Object
@@ -78,17 +83,57 @@ module RiderServer
78
83
  end
79
84
  end
80
85
 
86
+ def parent_constant(ns_object)
87
+ ns_object_name = ns_object.to_s
88
+ parent_name, _ = ns_object_name.rpartition("::")
89
+ parent_name.empty? ? nil : ::Object.const_get(parent_name)
90
+ end
91
+
81
92
  def all_constants(ns_object, prefix)
82
- ns_object.constants.grep(/^#{prefix}/).map do |constant|
93
+ consts = []
94
+ ns_object.constants.grep(/^#{prefix}/).each do |constant|
95
+ consts << constant.to_s
96
+ end
97
+
98
+ # Search up the tree to find all the other constants
99
+ constant = ns_object
100
+ loop do
101
+ constant = parent_constant(constant)
102
+ break if constant.nil?
103
+
104
+ constant.constants.grep(/^#{prefix}/).each do |constant|
105
+ consts << constant.to_s
106
+ end
107
+ end
108
+
109
+ consts.map do |constant|
83
110
  {
84
- "candidate" => constant.to_s,
111
+ "candidate" => constant,
85
112
  "type" => "constant"
86
113
  }
87
114
  end
88
115
  end
89
116
 
90
- def all_methods(ns_object, prefix)
91
- ns_object.methods.grep(/^#{prefix}/).map do |method|
117
+ ##
118
+ # Get all instance methods that match a +prefix+ for a given object +obj+
119
+ #
120
+ # This method should be called when in the context of an
121
+ # instance method, as all other instance methods will be
122
+ # acessable.
123
+ def class_instance_methods(obj, prefix)
124
+ Set.new(obj.instance_methods.grep(/^#{prefix}/))
125
+ end
126
+
127
+ def class_methods(obj, prefix)
128
+ Set.new(obj.methods.grep(/^#{prefix}/))
129
+ end
130
+
131
+ def class_singleton_methods(obj, prefix)
132
+ Set.new(obj.singleton_methods.grep(/^#{prefix}/))
133
+ end
134
+
135
+ def encode_methods(methods)
136
+ methods.map do |method|
92
137
  {
93
138
  "candidate" => method.to_s,
94
139
  "type" => "method"
@@ -46,8 +46,8 @@ module RiderServer
46
46
  response.set("ex", e.inspect)
47
47
  response.set("ns", session.workspace.evaluate("inspect"))
48
48
  response.status("eval-error", "done")
49
- exception_id = session.add_exception(operation["id"], e)
50
- response.set("rider/exception-id", exception_id)
49
+ exception = session.push_exception(operation["id"], e)
50
+ response.set("rider/exception-id", exception["id"])
51
51
  send_response(response)
52
52
  end
53
53
 
@@ -5,10 +5,11 @@ require "rider_server/inspect"
5
5
  module RiderServer
6
6
  module Ops
7
7
  class Lookup < Operation
8
- documentation "Get completions for a given string"
8
+ documentation "Get info about a symbol."
9
9
 
10
10
  argument :id, :string, "The request id", required: true
11
11
  argument :sym, :string, "The symbol to lookup", required: true
12
+ argument :lookup_fn, :string, "What lookup function to use, top-level, module, method or singleton-method"
12
13
  argument :ns, :string, "The namespace to search for completions"
13
14
 
14
15
  def initialize(*args)
@@ -20,63 +21,83 @@ module RiderServer
20
21
  response = Response.new(operation)
21
22
  ns = operation["ns"]
22
23
  sym = operation["sym"]
24
+ lookup_fn = operation.fetch("lookup-fn", "top-level")
23
25
 
24
- ns_object = @workspace.lookup_module(ns)
25
-
26
- method_type = nil
27
- if sym.include?("#")
28
- sym, method = sym.split("#")
29
- method_type = :instance
30
- method = method.to_sym
31
- elsif sym.start_with?("self.class")
32
- method = sym.split(".").last
33
- method_type = :class
34
- method = method.to_sym
35
- elsif sym.include?(".")
36
- sym, method = sym.split(".")
37
- method_type = :class
38
- method = method.to_sym
39
- end
26
+ if sym.nil? || parse_string(sym).nil?
27
+ response.set("info", {})
28
+ else
29
+ ns_object = if ns.empty?
30
+ ::Object
31
+ else
32
+ @workspace.lookup_module(ns)
33
+ end
40
34
 
41
- if sym.include?("::")
42
- ns_object = lookup_ns(sym.split("::"), ns_object)
35
+ # If the prefix isn't parseable, return an empty list
36
+ response.set("info", lookup_module(sym, klass: ns_object, lookup_fn: lookup_fn))
43
37
  end
44
- constant = ns_object
38
+ response.status("done")
39
+ response
40
+ end
45
41
 
46
- info = if sym.start_with?("@")
47
- constant = ns_object.instance_variable_get(sym)
48
- elsif method_type == :instance
49
- RiderServer::Inspect.describe_method(constant.instance_method(method))
50
- elsif method_type == :class
51
- RiderServer::Inspect.describe_method(constant.method(method))
52
- elsif constant.respond_to?(:instance_methods) && constant.instance_methods.include?(sym.to_sym)
53
- RiderServer::Inspect.describe_method(constant.instance_method(sym))
54
- elsif constant.respond_to?(:methods) && constant.methods.include?(sym.to_sym)
55
- RiderServer::Inspect.describe_method(constant.method(sym))
42
+ def lookup_module(module_name, klass: ::Object, lookup_fn: "top-level")
43
+ node = parse_string(module_name)
44
+ case node.type
45
+ when :const
46
+ ns = lookup_module_ast(node.children.first, klass)
47
+ name = node.children.last
48
+ encode_const(ns, name)
49
+ when :send
50
+ ns = lookup_module_ast(node.children.first, klass)
51
+ prefix = node.children.last
52
+ if lookup_fn == "method"
53
+ encode_method(ns.instance_method(prefix))
54
+ else
55
+ encode_method(ns.method(prefix))
56
+ end
56
57
  else
57
- {}
58
+ []
58
59
  end
60
+ end
59
61
 
60
- response.set("info", info)
61
- response.status("done")
62
- response
62
+ def parse_string(string)
63
+ buffer = Parser::Source::Buffer.new("(string)")
64
+ buffer.source = string
65
+ Parser::CurrentRuby.new.parse(buffer)
63
66
  end
64
67
 
65
- private
68
+ def lookup_module_ast(node, klass = ::Object)
69
+ return klass if node.nil?
66
70
 
67
- def lookup_ns(module_name, klass = Object)
68
- if module_name.start_with?("::")
69
- klass = Module
70
- module_name = module_name.delete_prefix("::")
71
+ case node.type
72
+ when :cbase
73
+ ::Object
74
+ when :const
75
+ if node.children.first.nil?
76
+ klass.const_get(node.children.last)
77
+ else
78
+ lookup_module_ast(node.children.first, klass).const_get(node.children.last)
79
+ end
80
+ else
81
+ raise ModuleLookupError, "Unknown node type #{node.type}"
71
82
  end
83
+ end
72
84
 
73
- path = module_name.split("::")
74
-
75
- path.inject(klass) do |acc, elem|
76
- acc.const_get(elem)
85
+ def encode_const(ns, const_name)
86
+ if ns.const_defined? const_name
87
+ {
88
+ "name" => "#{ns}::#{const_name}",
89
+ "source_location" => ns.const_source_location(const_name)
90
+ }
91
+ else
92
+ {}
77
93
  end
78
- rescue NameError
79
- nil
94
+ end
95
+
96
+ def encode_method(thing)
97
+ {
98
+ "name" => thing.name.to_s,
99
+ "source_location" => thing.source_location
100
+ }
80
101
  end
81
102
  end
82
103
  end
@@ -4,7 +4,7 @@ require "rider_server/response"
4
4
  module RiderServer
5
5
  module Ops
6
6
  class LsExceptions < Operation
7
- documentation "List all exceptions that have occurred in the session."
7
+ documentation "List all exceptions that exist in the sessions cache."
8
8
 
9
9
  argument :id, :string, "The request id", required: true
10
10
 
@@ -13,7 +13,7 @@ module RiderServer
13
13
  def handle(session, operation)
14
14
  response = Response.new(operation)
15
15
  name = operation["name"]
16
- raise "Name must be a valid Ruby identifier" unless name =~ /\A[a-zA-Z_]\w*\z/
16
+ raise "Name must be a valid Ruby identifier" unless /\A[a-zA-Z_]\w*\z/.match?(name)
17
17
  location = operation["location"]
18
18
 
19
19
  value = traverse_location(location, nil, session)
@@ -57,7 +57,7 @@ module RiderServer
57
57
 
58
58
  begin
59
59
  output = response.bencode
60
- rescue StandardError => e
60
+ rescue => e
61
61
  log.error "Error encoding response: #{e}"
62
62
  response = Response.new(response.to_h)
63
63
  response.set("ex", e.inspect)
@@ -28,11 +28,27 @@ module RiderServer
28
28
  }
29
29
  end
30
30
 
31
- def self.handle_exception(exception, metadata = {})
31
+ def self.create_exception_wrapper(exception, metadata)
32
+ id = SecureRandom.uuid
33
+ {
34
+ "id" => id,
35
+ "created_at" => DateTime.now,
36
+ "exception" => exception,
37
+ "metadata" => metadata
38
+ }
39
+ end
40
+
41
+ def self.handle_exception(exception, metadata: {})
42
+ wrapped_ex = create_exception_wrapper(exception, metadata)
43
+ # Firstly this needs to generate wrapper for the exception
44
+ # This needs to use push exception
45
+
32
46
  @sessions.each do |session, stream_id|
33
- ex = session.add_exception(stream_id, exception, metadata)
47
+ ex = session.push_anonymous_exception(wrapped_ex)
34
48
  session.response_queue.push(create_response(stream_id, ex))
35
49
  end
50
+
51
+ wrapped_ex
36
52
  end
37
53
 
38
54
  def initialize(session)
@@ -93,15 +93,20 @@ module RiderServer
93
93
  # Historical Exceptions
94
94
  #
95
95
 
96
- def add_exception(operation_id, exception, metadata = {})
96
+ def wrap_exception(operation_id, exception, metadata = {})
97
97
  id = SecureRandom.uuid
98
- @exceptions << {
98
+ {
99
99
  "id" => id,
100
100
  "operation_id" => operation_id,
101
101
  "created_at" => DateTime.now,
102
102
  "exception" => exception,
103
103
  "metadata" => metadata
104
104
  }
105
+ end
106
+
107
+ def add_exception(operation_id, exception, metadata = {})
108
+ id = SecureRandom.uuid
109
+ @exceptions << wrap_exception(operation_id, exception, metadata)
105
110
  id
106
111
  end
107
112
 
@@ -113,7 +118,14 @@ module RiderServer
113
118
 
114
119
  # A threadsafe way to add exceptions
115
120
  def push_exception(operation_id, exception, metadata = {})
116
- @exception_queue.push([operation_id, exception, metadata])
121
+ ex = wrap_exception(operation_id, exception, metadata)
122
+ @exception_queue.push(ex)
123
+ ex
124
+ end
125
+
126
+ def push_anonymous_exception(exception)
127
+ @exception_queue.push(exception)
128
+ exception
117
129
  end
118
130
 
119
131
  def start_exception_processing
@@ -121,7 +133,7 @@ module RiderServer
121
133
 
122
134
  @exception_processing_thread = Thread.new do
123
135
  loop do
124
- add_exception(*@exception_queue.pop)
136
+ @exceptions << @exception_queue.pop
125
137
  end
126
138
  end
127
139
  end
@@ -8,5 +8,5 @@
8
8
  # SPDX-License-Identifier: MIT
9
9
 
10
10
  module RiderServer
11
- VERSION = "0.1.0"
11
+ VERSION = "0.1.1"
12
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rider-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Russell Sim
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-27 00:00:00.000000000 Z
11
+ date: 2024-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bencode
@@ -50,6 +50,7 @@ files:
50
50
  - ".ruby-version"
51
51
  - ".standard.yml"
52
52
  - CHANGELOG.md
53
+ - COPYING
53
54
  - README.md
54
55
  - Rakefile
55
56
  - exe/rider-server