readapt 1.0.0 → 1.1.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 +4 -4
- data/.gitignore +16 -15
- data/.rspec +2 -2
- data/.travis.yml +18 -18
- data/CHANGELOG.md +72 -69
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +37 -29
- data/Rakefile +14 -14
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/exe/readapt +5 -5
- data/ext/readapt/breakpoints.c +83 -83
- data/ext/readapt/breakpoints.h +11 -11
- data/ext/readapt/extconf.rb +0 -0
- data/ext/readapt/frame.c +137 -137
- data/ext/readapt/frame.h +17 -17
- data/ext/readapt/hash_table.c +211 -211
- data/ext/readapt/hash_table.h +30 -30
- data/ext/readapt/inspector.c +51 -51
- data/ext/readapt/inspector.h +8 -8
- data/ext/readapt/monitor.c +1 -1
- data/ext/readapt/monitor.h +0 -0
- data/ext/readapt/normalize.c +59 -59
- data/ext/readapt/normalize.h +7 -7
- data/ext/readapt/readapt.c +18 -18
- data/ext/readapt/stack.c +86 -86
- data/ext/readapt/stack.h +20 -20
- data/ext/readapt/threads.c +15 -11
- data/ext/readapt/threads.h +1 -1
- data/lib/readapt.rb +21 -20
- data/lib/readapt/adapter.rb +98 -98
- data/lib/readapt/breakpoint.rb +20 -20
- data/lib/readapt/data_reader.rb +62 -62
- data/lib/readapt/debugger.rb +220 -220
- data/lib/readapt/error.rb +63 -63
- data/lib/readapt/finder.rb +20 -20
- data/lib/readapt/frame.rb +40 -40
- data/lib/readapt/input.rb +7 -7
- data/lib/readapt/message.rb +62 -62
- data/lib/readapt/message/attach.rb +11 -11
- data/lib/readapt/message/base.rb +32 -32
- data/lib/readapt/message/configuration_done.rb +11 -11
- data/lib/readapt/message/continue.rb +15 -15
- data/lib/readapt/message/disconnect.rb +13 -13
- data/lib/readapt/message/evaluate.rb +18 -18
- data/lib/readapt/message/initialize.rb +13 -13
- data/lib/readapt/message/launch.rb +11 -11
- data/lib/readapt/message/next.rb +12 -12
- data/lib/readapt/message/pause.rb +11 -11
- data/lib/readapt/message/scopes.rb +26 -26
- data/lib/readapt/message/set_breakpoints.rb +25 -25
- data/lib/readapt/message/set_exception_breakpoints.rb +8 -8
- data/lib/readapt/message/stack_trace.rb +38 -38
- data/lib/readapt/message/step_in.rb +11 -11
- data/lib/readapt/message/step_out.rb +11 -11
- data/lib/readapt/message/threads.rb +18 -18
- data/lib/readapt/message/variables.rb +61 -61
- data/lib/readapt/monitor.rb +0 -0
- data/lib/readapt/output.rb +25 -25
- data/lib/readapt/references.rb +27 -0
- data/lib/readapt/server.rb +22 -22
- data/lib/readapt/shell.rb +104 -104
- data/lib/readapt/snapshot.rb +0 -0
- data/lib/readapt/thread.rb +23 -23
- data/lib/readapt/variable.rb +1 -1
- data/lib/readapt/version.rb +3 -3
- data/readapt.gemspec +39 -39
- metadata +4 -3
data/ext/readapt/stack.h
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
-
#ifndef STACK_H_
|
2
|
-
#define STACK_H_
|
3
|
-
|
4
|
-
#include "stddef.h"
|
5
|
-
|
6
|
-
typedef struct readapt_stack_struct {
|
7
|
-
int size;
|
8
|
-
size_t elem_size;
|
9
|
-
void (*free_func)(void *);
|
10
|
-
int capacity;
|
11
|
-
void **elements;
|
12
|
-
} readapt_stack_t;
|
13
|
-
|
14
|
-
readapt_stack_t *stack_alloc(size_t elem_size, void(*free_func)(void*));
|
15
|
-
void stack_push(readapt_stack_t *stack, void *element);
|
16
|
-
void *stack_peek(readapt_stack_t *stack);
|
17
|
-
void stack_pop(readapt_stack_t *stack);
|
18
|
-
void stack_free(readapt_stack_t *stack);
|
19
|
-
|
20
|
-
#endif
|
1
|
+
#ifndef STACK_H_
|
2
|
+
#define STACK_H_
|
3
|
+
|
4
|
+
#include "stddef.h"
|
5
|
+
|
6
|
+
typedef struct readapt_stack_struct {
|
7
|
+
int size;
|
8
|
+
size_t elem_size;
|
9
|
+
void (*free_func)(void *);
|
10
|
+
int capacity;
|
11
|
+
void **elements;
|
12
|
+
} readapt_stack_t;
|
13
|
+
|
14
|
+
readapt_stack_t *stack_alloc(size_t elem_size, void(*free_func)(void*));
|
15
|
+
void stack_push(readapt_stack_t *stack, void *element);
|
16
|
+
void *stack_peek(readapt_stack_t *stack);
|
17
|
+
void stack_pop(readapt_stack_t *stack);
|
18
|
+
void stack_free(readapt_stack_t *stack);
|
19
|
+
|
20
|
+
#endif
|
data/ext/readapt/threads.c
CHANGED
@@ -6,13 +6,14 @@
|
|
6
6
|
|
7
7
|
static VALUE c_Thread;
|
8
8
|
static VALUE threads;
|
9
|
+
static VALUE ids;
|
10
|
+
static int next_id;
|
9
11
|
|
10
12
|
static void thread_reference_free(void* data)
|
11
13
|
{
|
12
14
|
thread_reference_t* thr;
|
13
15
|
|
14
16
|
thr = data;
|
15
|
-
// stack_free(thr->calls);
|
16
17
|
stack_free(thr->frames);
|
17
18
|
free(thr);
|
18
19
|
}
|
@@ -37,12 +38,12 @@ static VALUE thread_reference_new(VALUE thr)
|
|
37
38
|
{
|
38
39
|
thread_reference_t *data = malloc(sizeof(thread_reference_t));
|
39
40
|
VALUE obj = TypedData_Make_Struct(c_Thread, thread_reference_t, &thread_reference_type, data);
|
40
|
-
data->id =
|
41
|
+
data->id = next_id;
|
41
42
|
data->cursor = 0;
|
42
43
|
data->depth = 0;
|
43
44
|
data->control = rb_intern("continue");
|
44
45
|
data->frames = stack_alloc(sizeof(frame_t), frame_free);
|
45
|
-
|
46
|
+
next_id++;
|
46
47
|
return obj;
|
47
48
|
}
|
48
49
|
|
@@ -65,20 +66,24 @@ VALUE thread_reference(VALUE thr)
|
|
65
66
|
|
66
67
|
VALUE thread_reference_id(VALUE id)
|
67
68
|
{
|
68
|
-
return rb_hash_aref(
|
69
|
+
return rb_hash_aref(ids, id);
|
69
70
|
}
|
70
71
|
|
71
72
|
VALUE thread_add_reference(VALUE thr)
|
72
73
|
{
|
73
74
|
VALUE ref;
|
75
|
+
thread_reference_t *ptr;
|
74
76
|
|
75
77
|
ref = thread_reference_new(thr);
|
78
|
+
ptr = thread_reference_pointer(ref);
|
76
79
|
rb_hash_aset(threads, rb_obj_id(thr), ref);
|
80
|
+
rb_hash_aset(ids, INT2NUM(ptr->id), ref);
|
77
81
|
return ref;
|
78
82
|
}
|
79
83
|
|
80
84
|
VALUE thread_delete_reference(VALUE thr)
|
81
85
|
{
|
86
|
+
// TODO: Do we need to delete from ids here?
|
82
87
|
return rb_hash_delete(threads, rb_obj_id(thr));
|
83
88
|
}
|
84
89
|
|
@@ -101,6 +106,7 @@ void thread_pause()
|
|
101
106
|
void thread_clear()
|
102
107
|
{
|
103
108
|
rb_funcall(threads, rb_intern("clear"), 0);
|
109
|
+
rb_funcall(ids, rb_intern("clear"), 0);
|
104
110
|
}
|
105
111
|
|
106
112
|
static VALUE thread_allocate_s(VALUE self)
|
@@ -110,7 +116,6 @@ static VALUE thread_allocate_s(VALUE self)
|
|
110
116
|
data->depth = 0;
|
111
117
|
data->cursor = 0;
|
112
118
|
data->frames = stack_alloc(sizeof(frame_t), frame_free);
|
113
|
-
// data->calls = stack_alloc(sizeof(frame_t), NULL);
|
114
119
|
data->id = 0;
|
115
120
|
return TypedData_Wrap_Struct(self, &thread_reference_type, data);
|
116
121
|
}
|
@@ -127,7 +132,7 @@ static VALUE thread_find_s(VALUE self, VALUE id)
|
|
127
132
|
|
128
133
|
static VALUE thread_include_s(VALUE self, VALUE id)
|
129
134
|
{
|
130
|
-
return rb_funcall(
|
135
|
+
return rb_funcall(ids, rb_intern("include?"), 1, id);
|
131
136
|
}
|
132
137
|
|
133
138
|
static VALUE thread_id_m(VALUE self)
|
@@ -167,17 +172,14 @@ void thread_reference_build_frames(thread_reference_t *ptr)
|
|
167
172
|
|
168
173
|
void thread_reference_clear_frames(thread_reference_t *ptr)
|
169
174
|
{
|
170
|
-
// TODO This is probably suboptimal
|
171
|
-
// while (ptr->frames->size > 0)
|
172
|
-
// {
|
173
|
-
// stack_pop(ptr->frames);
|
174
|
-
// }
|
175
175
|
stack_free(ptr->frames);
|
176
176
|
ptr->frames = stack_alloc(sizeof(frame_t), frame_free);
|
177
177
|
}
|
178
178
|
|
179
179
|
void initialize_threads(VALUE m_Readapt)
|
180
180
|
{
|
181
|
+
next_id = 1;
|
182
|
+
|
181
183
|
c_Thread = rb_define_class_under(m_Readapt, "Thread", rb_cData);
|
182
184
|
rb_define_alloc_func(c_Thread, thread_allocate_s);
|
183
185
|
rb_define_method(c_Thread, "id", thread_id_m, 0);
|
@@ -188,4 +190,6 @@ void initialize_threads(VALUE m_Readapt)
|
|
188
190
|
|
189
191
|
threads = rb_hash_new();
|
190
192
|
rb_global_variable(&threads);
|
193
|
+
ids = rb_hash_new();
|
194
|
+
rb_global_variable(&ids);
|
191
195
|
}
|
data/ext/readapt/threads.h
CHANGED
data/lib/readapt.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
|
-
require 'backport'
|
2
|
-
|
3
|
-
require 'readapt/version'
|
4
|
-
require 'readapt/readapt'
|
5
|
-
require 'readapt/
|
6
|
-
require 'readapt/
|
7
|
-
require 'readapt/
|
8
|
-
require 'readapt/
|
9
|
-
require 'readapt/
|
10
|
-
require 'readapt/
|
11
|
-
require 'readapt/
|
12
|
-
require 'readapt/
|
13
|
-
require 'readapt/
|
14
|
-
require 'readapt/
|
15
|
-
require 'readapt/
|
16
|
-
require 'readapt/
|
17
|
-
require 'readapt/
|
18
|
-
require 'readapt/
|
19
|
-
require 'readapt/
|
20
|
-
require 'readapt/
|
1
|
+
require 'backport'
|
2
|
+
|
3
|
+
require 'readapt/version'
|
4
|
+
require 'readapt/readapt'
|
5
|
+
require 'readapt/references'
|
6
|
+
require 'readapt/breakpoint'
|
7
|
+
require 'readapt/thread'
|
8
|
+
require 'readapt/frame'
|
9
|
+
require 'readapt/monitor'
|
10
|
+
require 'readapt/snapshot'
|
11
|
+
require 'readapt/finder'
|
12
|
+
require 'readapt/debugger'
|
13
|
+
require 'readapt/message'
|
14
|
+
require 'readapt/variable'
|
15
|
+
require 'readapt/data_reader'
|
16
|
+
require 'readapt/server'
|
17
|
+
require 'readapt/adapter'
|
18
|
+
require 'readapt/input'
|
19
|
+
require 'readapt/output'
|
20
|
+
require 'readapt/error'
|
21
|
+
require 'readapt/shell'
|
data/lib/readapt/adapter.rb
CHANGED
@@ -1,98 +1,98 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Readapt
|
6
|
-
module Adapter
|
7
|
-
# @!parse include Backport::Adapter
|
8
|
-
|
9
|
-
@@debugger = nil
|
10
|
-
|
11
|
-
def self.host debugger
|
12
|
-
@@debugger = debugger
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.procid= pid
|
16
|
-
@@procid = pid
|
17
|
-
end
|
18
|
-
|
19
|
-
def procid
|
20
|
-
@@procid
|
21
|
-
end
|
22
|
-
|
23
|
-
def open_message
|
24
|
-
@@open_message ||= "<readapt-#{procid}>"
|
25
|
-
end
|
26
|
-
|
27
|
-
def close_message
|
28
|
-
@@close_message ||= "</readapt-#{procid}>"
|
29
|
-
end
|
30
|
-
|
31
|
-
def format result
|
32
|
-
write_line result.to_protocol.to_json
|
33
|
-
end
|
34
|
-
|
35
|
-
def opening
|
36
|
-
@@debugger.add_observer self
|
37
|
-
@data_reader = DataReader.new
|
38
|
-
@data_reader.set_message_handler do |message|
|
39
|
-
process message
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def closing
|
44
|
-
@@debugger.delete_observer(self)
|
45
|
-
end
|
46
|
-
|
47
|
-
def receiving data
|
48
|
-
@data_reader.receive data
|
49
|
-
end
|
50
|
-
|
51
|
-
def update event, data
|
52
|
-
obj = {
|
53
|
-
type: 'event',
|
54
|
-
event: event
|
55
|
-
}
|
56
|
-
obj[:body] = data unless data.nil?
|
57
|
-
json = obj.to_json
|
58
|
-
envelope = "#{open_message}Content-Length: #{json.bytesize}\r\n\r\n#{json}#{close_message}"
|
59
|
-
write envelope
|
60
|
-
write "#{open_message}__TERMINATE__#{close_message}" if event == 'terminated'
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
# @param data [Hash]
|
66
|
-
# @return [void]
|
67
|
-
def process data
|
68
|
-
message = Message.process(data, @@debugger)
|
69
|
-
if data['seq']
|
70
|
-
json = {
|
71
|
-
type: 'response',
|
72
|
-
request_seq: data['seq'],
|
73
|
-
success: true,
|
74
|
-
command: data['command'],
|
75
|
-
body: message.body
|
76
|
-
}.to_json
|
77
|
-
envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
78
|
-
write "#{open_message}#{envelope}#{close_message}"
|
79
|
-
if data['command'] == 'disconnect'
|
80
|
-
@@debugger.disconnect
|
81
|
-
# @todo It does not appear necessary to close the adapter after
|
82
|
-
# disconnecting the debugger.
|
83
|
-
# close
|
84
|
-
end
|
85
|
-
return unless data['command'] == 'initialize'
|
86
|
-
json = {
|
87
|
-
type: 'event',
|
88
|
-
event: 'initialized'
|
89
|
-
}.to_json
|
90
|
-
envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
91
|
-
write "#{open_message}#{envelope}#{close_message}"
|
92
|
-
end
|
93
|
-
rescue RuntimeError => e
|
94
|
-
STDERR.puts "[#{e.class}] #{e.message}"
|
95
|
-
STDERR.puts e.backtrace.join
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Readapt
|
6
|
+
module Adapter
|
7
|
+
# @!parse include Backport::Adapter
|
8
|
+
|
9
|
+
@@debugger = nil
|
10
|
+
|
11
|
+
def self.host debugger
|
12
|
+
@@debugger = debugger
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.procid= pid
|
16
|
+
@@procid = pid
|
17
|
+
end
|
18
|
+
|
19
|
+
def procid
|
20
|
+
@@procid
|
21
|
+
end
|
22
|
+
|
23
|
+
def open_message
|
24
|
+
@@open_message ||= "<readapt-#{procid}>"
|
25
|
+
end
|
26
|
+
|
27
|
+
def close_message
|
28
|
+
@@close_message ||= "</readapt-#{procid}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
def format result
|
32
|
+
write_line result.to_protocol.to_json
|
33
|
+
end
|
34
|
+
|
35
|
+
def opening
|
36
|
+
@@debugger.add_observer self
|
37
|
+
@data_reader = DataReader.new
|
38
|
+
@data_reader.set_message_handler do |message|
|
39
|
+
process message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def closing
|
44
|
+
@@debugger.delete_observer(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def receiving data
|
48
|
+
@data_reader.receive data
|
49
|
+
end
|
50
|
+
|
51
|
+
def update event, data
|
52
|
+
obj = {
|
53
|
+
type: 'event',
|
54
|
+
event: event
|
55
|
+
}
|
56
|
+
obj[:body] = data unless data.nil?
|
57
|
+
json = obj.to_json
|
58
|
+
envelope = "#{open_message}Content-Length: #{json.bytesize}\r\n\r\n#{json}#{close_message}"
|
59
|
+
write envelope
|
60
|
+
write "#{open_message}__TERMINATE__#{close_message}" if event == 'terminated'
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# @param data [Hash]
|
66
|
+
# @return [void]
|
67
|
+
def process data
|
68
|
+
message = Message.process(data, @@debugger)
|
69
|
+
if data['seq']
|
70
|
+
json = {
|
71
|
+
type: 'response',
|
72
|
+
request_seq: data['seq'],
|
73
|
+
success: true,
|
74
|
+
command: data['command'],
|
75
|
+
body: message.body
|
76
|
+
}.to_json
|
77
|
+
envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
78
|
+
write "#{open_message}#{envelope}#{close_message}"
|
79
|
+
if data['command'] == 'disconnect'
|
80
|
+
@@debugger.disconnect
|
81
|
+
# @todo It does not appear necessary to close the adapter after
|
82
|
+
# disconnecting the debugger.
|
83
|
+
# close
|
84
|
+
end
|
85
|
+
return unless data['command'] == 'initialize'
|
86
|
+
json = {
|
87
|
+
type: 'event',
|
88
|
+
event: 'initialized'
|
89
|
+
}.to_json
|
90
|
+
envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
91
|
+
write "#{open_message}#{envelope}#{close_message}"
|
92
|
+
end
|
93
|
+
rescue RuntimeError => e
|
94
|
+
STDERR.puts "[#{e.class}] #{e.message}"
|
95
|
+
STDERR.puts e.backtrace.join
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/readapt/breakpoint.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
-
module Readapt
|
2
|
-
class Breakpoint
|
3
|
-
attr_reader :source
|
4
|
-
attr_reader :line
|
5
|
-
attr_reader :condition
|
6
|
-
attr_reader :hit_condition
|
7
|
-
attr_writer :hit_cursor
|
8
|
-
|
9
|
-
def initialize source, line, condition, hit_condition
|
10
|
-
@source = source
|
11
|
-
@line = line
|
12
|
-
@condition = condition
|
13
|
-
@hit_condition = hit_condition
|
14
|
-
end
|
15
|
-
|
16
|
-
def hit_cursor
|
17
|
-
@hit_cursor ||= 0
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
1
|
+
module Readapt
|
2
|
+
class Breakpoint
|
3
|
+
attr_reader :source
|
4
|
+
attr_reader :line
|
5
|
+
attr_reader :condition
|
6
|
+
attr_reader :hit_condition
|
7
|
+
attr_writer :hit_cursor
|
8
|
+
|
9
|
+
def initialize source, line, condition, hit_condition
|
10
|
+
@source = source
|
11
|
+
@line = line
|
12
|
+
@condition = condition
|
13
|
+
@hit_condition = hit_condition
|
14
|
+
end
|
15
|
+
|
16
|
+
def hit_cursor
|
17
|
+
@hit_cursor ||= 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/readapt/data_reader.rb
CHANGED
@@ -1,62 +1,62 @@
|
|
1
|
-
module Readapt
|
2
|
-
class DataReader
|
3
|
-
def initialize
|
4
|
-
@in_header = true
|
5
|
-
@content_length = 0
|
6
|
-
@buffer = String.new
|
7
|
-
end
|
8
|
-
|
9
|
-
# Declare a block to be executed for each message received from the
|
10
|
-
# client.
|
11
|
-
#
|
12
|
-
# @yieldparam [Hash] The message received from the client
|
13
|
-
def set_message_handler &block
|
14
|
-
@message_handler = block
|
15
|
-
end
|
16
|
-
|
17
|
-
# Process raw data received from the client. The data will be parsed
|
18
|
-
# into messages based on the JSON-RPC protocol. Each message will be
|
19
|
-
# passed to the block declared via set_message_handler. Incomplete data
|
20
|
-
# will be buffered and subsequent data will be appended to the buffer.
|
21
|
-
#
|
22
|
-
# @param data [String]
|
23
|
-
def receive data
|
24
|
-
data.each_char do |char|
|
25
|
-
@buffer.concat char
|
26
|
-
if @in_header
|
27
|
-
prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
|
28
|
-
else
|
29
|
-
parse_message_from_buffer if @buffer.bytesize == @content_length
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def prepare_to_parse_message
|
37
|
-
@in_header = false
|
38
|
-
@buffer.each_line do |line|
|
39
|
-
parts = line.split(':').map(&:strip)
|
40
|
-
if parts[0] == 'Content-Length'
|
41
|
-
@content_length = parts[1].to_i
|
42
|
-
break
|
43
|
-
end
|
44
|
-
end
|
45
|
-
@buffer.clear
|
46
|
-
end
|
47
|
-
|
48
|
-
def parse_message_from_buffer
|
49
|
-
begin
|
50
|
-
msg = JSON.parse(@buffer)
|
51
|
-
@message_handler.call msg unless @message_handler.nil?
|
52
|
-
rescue JSON::ParserError => e
|
53
|
-
Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
|
54
|
-
Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
|
55
|
-
ensure
|
56
|
-
@buffer.clear
|
57
|
-
@in_header = true
|
58
|
-
@content_length = 0
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
1
|
+
module Readapt
|
2
|
+
class DataReader
|
3
|
+
def initialize
|
4
|
+
@in_header = true
|
5
|
+
@content_length = 0
|
6
|
+
@buffer = String.new
|
7
|
+
end
|
8
|
+
|
9
|
+
# Declare a block to be executed for each message received from the
|
10
|
+
# client.
|
11
|
+
#
|
12
|
+
# @yieldparam [Hash] The message received from the client
|
13
|
+
def set_message_handler &block
|
14
|
+
@message_handler = block
|
15
|
+
end
|
16
|
+
|
17
|
+
# Process raw data received from the client. The data will be parsed
|
18
|
+
# into messages based on the JSON-RPC protocol. Each message will be
|
19
|
+
# passed to the block declared via set_message_handler. Incomplete data
|
20
|
+
# will be buffered and subsequent data will be appended to the buffer.
|
21
|
+
#
|
22
|
+
# @param data [String]
|
23
|
+
def receive data
|
24
|
+
data.each_char do |char|
|
25
|
+
@buffer.concat char
|
26
|
+
if @in_header
|
27
|
+
prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
|
28
|
+
else
|
29
|
+
parse_message_from_buffer if @buffer.bytesize == @content_length
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def prepare_to_parse_message
|
37
|
+
@in_header = false
|
38
|
+
@buffer.each_line do |line|
|
39
|
+
parts = line.split(':').map(&:strip)
|
40
|
+
if parts[0] == 'Content-Length'
|
41
|
+
@content_length = parts[1].to_i
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@buffer.clear
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_message_from_buffer
|
49
|
+
begin
|
50
|
+
msg = JSON.parse(@buffer)
|
51
|
+
@message_handler.call msg unless @message_handler.nil?
|
52
|
+
rescue JSON::ParserError => e
|
53
|
+
Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
|
54
|
+
Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
|
55
|
+
ensure
|
56
|
+
@buffer.clear
|
57
|
+
@in_header = true
|
58
|
+
@content_length = 0
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|