rider-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.build.yml +23 -0
  3. data/.ruby-version +1 -0
  4. data/.standard.yml +3 -0
  5. data/CHANGELOG.md +5 -0
  6. data/README.md +44 -0
  7. data/Rakefile +12 -0
  8. data/exe/rider-server +11 -0
  9. data/lib/rider_server/core_ext/array.rb +32 -0
  10. data/lib/rider_server/core_ext/hash.rb +14 -0
  11. data/lib/rider_server/core_ext/object.rb +18 -0
  12. data/lib/rider_server/core_ext/string.rb +18 -0
  13. data/lib/rider_server/core_ext/symbol.rb +14 -0
  14. data/lib/rider_server/errors.rb +16 -0
  15. data/lib/rider_server/exception_extension.rb +34 -0
  16. data/lib/rider_server/inspect.rb +148 -0
  17. data/lib/rider_server/logger.rb +13 -0
  18. data/lib/rider_server/operation.rb +69 -0
  19. data/lib/rider_server/operations.rb +136 -0
  20. data/lib/rider_server/ops/clone.rb +32 -0
  21. data/lib/rider_server/ops/close.rb +25 -0
  22. data/lib/rider_server/ops/completions.rb +100 -0
  23. data/lib/rider_server/ops/eval.rb +62 -0
  24. data/lib/rider_server/ops/inspect.rb +121 -0
  25. data/lib/rider_server/ops/inspect_exception.rb +47 -0
  26. data/lib/rider_server/ops/interrupt.rb +30 -0
  27. data/lib/rider_server/ops/load_path.rb +20 -0
  28. data/lib/rider_server/ops/lookup.rb +83 -0
  29. data/lib/rider_server/ops/ls_exceptions.rb +29 -0
  30. data/lib/rider_server/ops/ls_services.rb +19 -0
  31. data/lib/rider_server/ops/ls_sessions.rb +52 -0
  32. data/lib/rider_server/ops/service.rb +43 -0
  33. data/lib/rider_server/ops/set_namespace.rb +79 -0
  34. data/lib/rider_server/ops/set_namespace_variable.rb +80 -0
  35. data/lib/rider_server/ops/stdin.rb +20 -0
  36. data/lib/rider_server/ops/toggle_catch_all_exceptions.rb +27 -0
  37. data/lib/rider_server/response.rb +69 -0
  38. data/lib/rider_server/server.rb +104 -0
  39. data/lib/rider_server/service.rb +20 -0
  40. data/lib/rider_server/services/capture_exceptions.rb +62 -0
  41. data/lib/rider_server/services/capture_io.rb +302 -0
  42. data/lib/rider_server/services/rails.rb +129 -0
  43. data/lib/rider_server/session.rb +190 -0
  44. data/lib/rider_server/transports/bencode.rb +0 -0
  45. data/lib/rider_server/utils.rb +63 -0
  46. data/lib/rider_server/version.rb +12 -0
  47. data/lib/rider_server/workspace.rb +111 -0
  48. data/lib/rider_server.rb +5 -0
  49. metadata +122 -0
@@ -0,0 +1,25 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Close < Operation
7
+ documentation "Close a specific session."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :session, :string, "The session to close", required: true
11
+
12
+ # Handle the close operation, session will be nil
13
+ def handle(session, operation)
14
+ response = Response.new(operation)
15
+
16
+ session_id = operation["session"]
17
+
18
+ controller.delete_session(session_id)
19
+
20
+ response.status("done")
21
+ response
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+ require "rider_server/inspect"
4
+
5
+ module RiderServer
6
+ module Ops
7
+ class Completions < Operation
8
+ documentation "Get completions for a given prefix"
9
+
10
+ argument :id, :string, "The request id", required: true
11
+ argument :prefix, :string, "The prefix to complete", required: true
12
+ argument :ns, :string, "The namespace to search for completions"
13
+
14
+ def initialize(*args)
15
+ @workspace = Workspace.new
16
+ super
17
+ end
18
+
19
+ def handle(session, operation)
20
+ response = Response.new(operation)
21
+
22
+ prefix = operation["prefix"]
23
+ log.info "prefix: #{prefix}"
24
+ ns = operation["ns"]
25
+ # options = operation["options"]
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"]}"
33
+
34
+ response.status("done")
35
+ response
36
+ rescue ScriptError, StandardError => e
37
+ log.error "Error in completions: #{e}"
38
+ log.error e.backtrace.join("\n")
39
+ raise e
40
+ end
41
+
42
+ private
43
+
44
+ def lookup_module(module_name, klass = ::Object)
45
+ node = parse_string(module_name)
46
+ case node.type
47
+ when :const
48
+ ns = lookup_module_ast(node.children.first, klass)
49
+ prefix = node.children.last
50
+ all_constants(ns, prefix) + all_methods(ns, prefix)
51
+ when :send
52
+ ns = lookup_module_ast(node.children.first, klass)
53
+ prefix = node.children.last
54
+ all_methods(ns, prefix)
55
+ else
56
+ []
57
+ end
58
+ end
59
+
60
+ def parse_string(string)
61
+ buffer = Parser::Source::Buffer.new("(string)")
62
+ buffer.source = string
63
+ Parser::CurrentRuby.new.parse(buffer)
64
+ end
65
+
66
+ def lookup_module_ast(node, klass = ::Object)
67
+ case node.type
68
+ when :cbase
69
+ ::Object
70
+ when :const
71
+ if node.children.first.nil?
72
+ klass.const_get(node.children.last)
73
+ else
74
+ lookup_module_ast(node.children.first, klass).const_get(node.children.last)
75
+ end
76
+ else
77
+ raise ModuleLookupError, "Unknown node type #{node.type}"
78
+ end
79
+ end
80
+
81
+ def all_constants(ns_object, prefix)
82
+ ns_object.constants.grep(/^#{prefix}/).map do |constant|
83
+ {
84
+ "candidate" => constant.to_s,
85
+ "type" => "constant"
86
+ }
87
+ end
88
+ end
89
+
90
+ def all_methods(ns_object, prefix)
91
+ ns_object.methods.grep(/^#{prefix}/).map do |method|
92
+ {
93
+ "candidate" => method.to_s,
94
+ "type" => "method"
95
+ }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,62 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+ require "rider_server/utils"
4
+
5
+ module RiderServer
6
+ module Ops
7
+ class Eval < Operation
8
+ documentation "Evaluate a string of code"
9
+
10
+ argument :id, :string, "The request id", required: true
11
+ argument :code, :string, "The code to evaluate"
12
+ argument :ns, :string, "The namespace to evaluate the code in"
13
+ argument :file, :string, "The file the code is from"
14
+ argument :line, :integer, "The line number the code is from"
15
+
16
+ def handle(session, operation)
17
+ # TODO should do something if the session is nil
18
+
19
+ response = Response.new(operation)
20
+
21
+ # Abort if there is an evaluation with the same id
22
+ if session.running_evaluation?(operation["id"])
23
+ msg = "Evaluation already in progress for #{operation["id"]}"
24
+ log.warn(msg)
25
+ response.set("value", msg)
26
+ response.status("eval-error", "done")
27
+ send_response(response)
28
+ return
29
+ end
30
+
31
+ session.push_history(operation)
32
+ code = operation["code"]
33
+ ns = operation["ns"]
34
+ file = operation["file"] || ""
35
+ line = operation["line"] || 0
36
+
37
+ eval_thread = Thread.new do
38
+ begin
39
+ value = session.workspace.evaluate(code, ns, file, line)
40
+ response.set("value", Utils.rider_inspect(value))
41
+ response.set("ns", session.workspace.evaluate("inspect"))
42
+ response.status("done")
43
+ session.add_result(operation["id"], value)
44
+ send_response(response)
45
+ rescue EvalInterrupt, ScriptError, StandardError => e
46
+ response.set("ex", e.inspect)
47
+ response.set("ns", session.workspace.evaluate("inspect"))
48
+ response.status("eval-error", "done")
49
+ exception_id = session.add_exception(operation["id"], e)
50
+ response.set("rider/exception-id", exception_id)
51
+ send_response(response)
52
+ end
53
+
54
+ session.remove_evaluation(operation["id"])
55
+ end
56
+
57
+ session.add_evaluation(operation["id"], eval_thread)
58
+ nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,121 @@
1
+ require "rider_server/inspect"
2
+ require "rider_server/operation"
3
+ require "rider_server/response"
4
+ require "rider_server/utils"
5
+
6
+ module RiderServer
7
+ module Ops
8
+ class Inspect < Operation
9
+ documentation "Inspect an object"
10
+
11
+ argument :id, :string, "The request id", required: true
12
+ argument :location, :array, "The location of the object to inspect"
13
+
14
+ def handle(session, operation)
15
+ response = Response.new(operation)
16
+ location = operation["location"]
17
+ raise "Location is required" if location.nil? || location.empty?
18
+
19
+ value = traverse_location(location, nil, session)
20
+ response.set("name", Utils.rider_inspect(value))
21
+
22
+ if value.is_a?(Array)
23
+ value = value.map.with_index do |item, index|
24
+ {
25
+ "name" => item.to_s,
26
+ "value" => Utils.rider_inspect(item),
27
+ "inspect-location" => "rider_array_item:#{index}"
28
+ }
29
+ end
30
+ response.set("value-array", value)
31
+ elsif value.is_a?(Hash)
32
+ value = value.map do |key, value|
33
+ [
34
+ {
35
+ "name" => key.to_s,
36
+ "value" => Utils.rider_inspect(key),
37
+ "inspect-location" => "rider_hash_key:#{key.hash}"
38
+ },
39
+ {
40
+ "name" => value.to_s,
41
+ "value" => Utils.rider_inspect(value),
42
+ "inspect-location" => "rider_hash_value:#{key.hash}"
43
+ }
44
+ ]
45
+ end
46
+ response.set("value-hash", value)
47
+ elsif value.is_a?(String)
48
+ response.set("value", value.inspect)
49
+ elsif value.is_a?(Numeric)
50
+ response.set("value", value.to_s)
51
+ end
52
+
53
+ response.set("inspect-location", location)
54
+
55
+ response.set("class", RiderServer::Inspect.class(value))
56
+ response.set("ancestors", RiderServer::Inspect.ancestors(value))
57
+ response.set("constants", RiderServer::Inspect.constants(value))
58
+ response.set("methods", RiderServer::Inspect.methods(value))
59
+ response.set("instance-variables", RiderServer::Inspect.instance_variables(value))
60
+ response.set("instance-methods", RiderServer::Inspect.instance_methods(value))
61
+ response.set("class-variables", RiderServer::Inspect.class_variables(value))
62
+ response.status("done")
63
+ response
64
+ end
65
+
66
+ def traverse_location(location, ctx, session)
67
+ loc = location.first
68
+ locs = location.drop(1)
69
+
70
+ if locs.empty?
71
+ lookup_object(loc, ctx, session)
72
+ else
73
+ traverse_location(locs, lookup_object(loc, ctx, session), session)
74
+ end
75
+ end
76
+
77
+ def lookup_object(loc, ctx, session)
78
+ lookup, identifier = loc.split(":", 2)
79
+
80
+ case lookup
81
+ when "rider_main"
82
+ "main"
83
+ when "rider_history"
84
+ rider_history(identifier, nil, session)
85
+ when "rider_exception"
86
+ rider_exception(identifier, nil, session)
87
+ when "rider_stackframe_variable"
88
+ frame_id, local_var = identifier.split(":", 2)
89
+ ctx.__rider_bindings_stack[frame_id.to_i].local_variable_get(local_var)
90
+ when "rider_array_item"
91
+ ctx[identifier.to_i]
92
+ when "rider_hash_key"
93
+ hash = identifier.to_i
94
+ ctx.keys.find { |key| key.hash == hash }
95
+ when "rider_hash_value"
96
+ hash = identifier.to_i
97
+ key = ctx.keys.find { |key| key.hash == hash }
98
+ ctx.fetch(key)
99
+ when "toplevel_const_get"
100
+ Object.const_get(identifier)
101
+ when "instance_variable_get"
102
+ ctx.instance_variable_get(identifier)
103
+ when "const_get"
104
+ ctx.const_get(identifier)
105
+ when "ancestor_find"
106
+ ctx.class.ancestors.find { |item| item.to_s == identifier }
107
+ else
108
+ raise "Unknown inspect function: #{lookup}"
109
+ end
110
+ end
111
+
112
+ def rider_history(id, ctx, session)
113
+ session.get_result(id)
114
+ end
115
+
116
+ def rider_exception(id, ctx, session)
117
+ session.get_exception(id)["exception"]
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,47 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class InspectException < Operation
7
+ documentation "Inspect an exception."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :"exception-id", :string, "The exception id"
11
+
12
+ def handle(session, operation)
13
+ response = Response.new(operation)
14
+ exception_id = operation["exception-id"]
15
+
16
+ value = session.get_exception(exception_id)
17
+ response.set("inspect-location", value["id"])
18
+ response.set("exception-id", value["id"])
19
+ response.set("source-operation-id", value["operation_id"])
20
+ response.set("created-at", value["created_at"].to_s)
21
+
22
+ exception = value["exception"]
23
+ response.set("value", exception.inspect)
24
+ response.set("stacktrace", encode_stacktrace(exception, exception_id))
25
+
26
+ response.status("done")
27
+ response
28
+ end
29
+
30
+ def encode_stacktrace(exception, exception_id)
31
+ exception.backtrace.zip(exception.__rider_bindings_stack).map.with_index do |(line, frame), frame_id|
32
+ {
33
+ "line" => line,
34
+ "inspect-location" => ["rider_exception:#{exception_id}", "rider_stackframe:#{frame_id}"],
35
+ "frame" => (frame&.local_variables || []).map do |var|
36
+ {
37
+ "name" => var.to_s,
38
+ "value" => Utils.rider_inspect(frame.local_variable_get(var)),
39
+ "inspect-location" => ["rider_exception:#{exception_id}", "rider_stackframe_variable:#{frame_id}:#{var}"]
40
+ }
41
+ end
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Interrupt < Operation
7
+ documentation "Interrupts the evaluation of a session."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :"interrupt-id", :string, "The ID of the evaluation to interrupt.", required: true
11
+
12
+ def handle(session, operation)
13
+ # TODO should do something if the session is nil
14
+
15
+ if operation["interrupt-id"].nil? || operation["interrupt-id"].empty?
16
+ if session.evaluations.empty?
17
+ raise ArgumentError, "No evaluations to interrupt"
18
+ end
19
+ session.interrupt_evaluation(session.evaluations.keys.max)
20
+ else
21
+ session.interrupt_evaluation(operation["interrupt-id"])
22
+ end
23
+
24
+ response = Response.new(operation)
25
+ response.status("interrupted", "done")
26
+ response
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LoadPath < Operation
7
+ documentation "Return the current load path."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+ load_path = session.workspace.evaluate("$LOAD_PATH", "main")
14
+ response.set("load-path", load_path)
15
+ response.status("done")
16
+ response
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,83 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+ require "rider_server/inspect"
4
+
5
+ module RiderServer
6
+ module Ops
7
+ class Lookup < Operation
8
+ documentation "Get completions for a given string"
9
+
10
+ argument :id, :string, "The request id", required: true
11
+ argument :sym, :string, "The symbol to lookup", required: true
12
+ argument :ns, :string, "The namespace to search for completions"
13
+
14
+ def initialize(*args)
15
+ @workspace = Workspace.new
16
+ super
17
+ end
18
+
19
+ def handle(session, operation)
20
+ response = Response.new(operation)
21
+ ns = operation["ns"]
22
+ sym = operation["sym"]
23
+
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
40
+
41
+ if sym.include?("::")
42
+ ns_object = lookup_ns(sym.split("::"), ns_object)
43
+ end
44
+ constant = ns_object
45
+
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))
56
+ else
57
+ {}
58
+ end
59
+
60
+ response.set("info", info)
61
+ response.status("done")
62
+ response
63
+ end
64
+
65
+ private
66
+
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
+ end
72
+
73
+ path = module_name.split("::")
74
+
75
+ path.inject(klass) do |acc, elem|
76
+ acc.const_get(elem)
77
+ end
78
+ rescue NameError
79
+ nil
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,29 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LsExceptions < Operation
7
+ documentation "List all exceptions that have occurred in the session."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+
14
+ exceptions = session.exceptions.map do |item|
15
+ {
16
+ "id" => item["id"],
17
+ "operation-id" => item["operation_id"],
18
+ "created-at" => item["created_at"].iso8601,
19
+ "exception" => item["exception"].inspect
20
+ }
21
+ end
22
+
23
+ response.set("exceptions", exceptions)
24
+ response.status("done")
25
+ response
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LsServices < Operation
7
+ documentation "List all services."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+ response.set("services", session.list_services)
14
+ response.status("done")
15
+ response
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LsSessions < Operation
7
+ documentation "List all sessions"
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+ response.set("sessions", controller.sessions.map { |k, v| k })
14
+ response.set("rider/session-headings",
15
+ [
16
+ {
17
+ "id" => "id",
18
+ "name" => "ID",
19
+ "length" => 36
20
+ },
21
+ {
22
+ "id" => "namespace_name",
23
+ "name" => "Namespace",
24
+ "length" => 25
25
+ },
26
+ {
27
+ "id" => "history_items",
28
+ "name" => "History Items",
29
+ "length" => 5
30
+ },
31
+ {
32
+ "id" => "exceptions",
33
+ "name" => "Exceptions",
34
+ "length" => 5
35
+ }
36
+ ])
37
+ response.set("rider/sessions", controller.sessions.map { |k, v| session_summary(v) })
38
+ response.status("done")
39
+ response
40
+ end
41
+
42
+ def session_summary(session)
43
+ {
44
+ "id" => session.id,
45
+ "namespace_name" => session.workspace.namespace_name,
46
+ "history_items" => session.history.length.to_s,
47
+ "exceptions" => session.exceptions.length.to_s
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Service < Operation
7
+ documentation "Control a Ruby service integration"
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :service, :string, "The name of the Ruby service to control", required: true
11
+ argument :state, :string, "The desired state of the service", required: true
12
+
13
+ def handle(session, operation)
14
+ response = Response.new(operation)
15
+
16
+ service = operation["service"]
17
+ state = operation["state"]
18
+
19
+ current_state = session.service_state(service)
20
+
21
+ case state
22
+ when "start"
23
+ raise "Service already running" if current_state == "running"
24
+ session.start_service(service, response.id)
25
+ response.set("rider/stream", "true")
26
+ when "stop"
27
+ raise "Can't stop service #{service}. It's not running." if current_state == "stopped"
28
+ session.stop_service(service)
29
+ response.status("done")
30
+ else
31
+ # TODO: throwing this exception will caues a "done" response
32
+ # to be sent, which will implicitly close the stream. It
33
+ # might make sense to handle this more gracefully here.
34
+ raise "Unknown state #{state}"
35
+ end
36
+
37
+ response.set("service", service)
38
+ response.set("state", session.service_state(service).to_s)
39
+ response
40
+ end
41
+ end
42
+ end
43
+ end