rtext 0.8.0 → 0.9.3

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +113 -84
  3. data/Project.yaml +14 -0
  4. data/RText_Protocol +47 -4
  5. data/lib/rtext/context_builder.rb +49 -8
  6. data/lib/rtext/default_completer.rb +212 -163
  7. data/lib/rtext/default_service_provider.rb +3 -3
  8. data/lib/rtext/frontend/connector.rb +122 -55
  9. data/lib/rtext/frontend/context.rb +12 -12
  10. data/lib/rtext/instantiator.rb +11 -3
  11. data/lib/rtext/language.rb +5 -5
  12. data/lib/rtext/serializer.rb +1 -1
  13. data/lib/rtext/service.rb +264 -253
  14. data/lib/rtext/tokenizer.rb +1 -1
  15. metadata +18 -43
  16. data/Rakefile +0 -46
  17. data/test/completer_test.rb +0 -606
  18. data/test/context_builder_test.rb +0 -948
  19. data/test/frontend/context_test.rb +0 -205
  20. data/test/instantiator_test.rb +0 -1691
  21. data/test/integration/backend.out +0 -13
  22. data/test/integration/crash_on_request_editor.rb +0 -12
  23. data/test/integration/ecore_editor.rb +0 -50
  24. data/test/integration/frontend.log +0 -36049
  25. data/test/integration/model/invalid_encoding.invenc +0 -2
  26. data/test/integration/model/test.crash_on_request +0 -18
  27. data/test/integration/model/test.crashing_backend +0 -18
  28. data/test/integration/model/test.dont_open_socket +0 -0
  29. data/test/integration/model/test.invalid_cmd_line +0 -0
  30. data/test/integration/model/test.not_in_rtext +0 -0
  31. data/test/integration/model/test_large_with_errors.ect3 +0 -43523
  32. data/test/integration/model/test_metamodel.ect +0 -24
  33. data/test/integration/model/test_metamodel2.ect +0 -5
  34. data/test/integration/model/test_metamodel_error.ect2 +0 -3
  35. data/test/integration/model/test_metamodel_ok.ect2 +0 -18
  36. data/test/integration/test.rb +0 -918
  37. data/test/link_detector_test.rb +0 -287
  38. data/test/message_helper_test.rb +0 -118
  39. data/test/rtext_test.rb +0 -11
  40. data/test/serializer_test.rb +0 -1004
  41. data/test/tokenizer_test.rb +0 -173
@@ -55,27 +55,27 @@ def filter_lines(lines)
55
55
  }
56
56
  end
57
57
 
58
- # when joining two lines, all whitespace after the last character of the first line is removed
59
- # (after , and [); however whitespace at the end of the last of several joined lines is preserved;
60
- # this way the context is correct even if the cursor is after the end of the last line
61
- # (i.e. with whitespace after the last non-whitespace character)
58
+ # when joining two lines, all whitespace is preserved in order to simplify the algorithm
59
+ # whitespace after a backslash is also preserved, only the backslash itself is removed
60
+ # note that whitespace left of the cursor is important for proper context calculation
62
61
  def join_lines(lines, pos)
63
62
  outlines = []
64
63
  while lines.size > 0
65
64
  outlines << lines.shift
66
65
  while lines.size > 0 &&
67
- (outlines.last =~ /,\s*$/ ||
68
- (outlines.last =~ /\[\s*$/ && outlines.last =~ /,/) ||
69
- (lines.first =~ /^\s*\]/ && outlines.last =~ /,/))
70
- outlines.last.rstrip!
66
+ (outlines.last =~ /[,\\]\s*$/ ||
67
+ # don't join after a child label
68
+ (outlines.last !~ /^\s*\w+:/ &&
69
+ (outlines.last =~ /\[\s*$/ ||
70
+ (lines.first =~ /^\s*\]/ && outlines.last =~ /\[/))))
71
71
  l = lines.shift
72
+ outlines.last.gsub!("\\","")
72
73
  if lines.size == 0
73
- # strip only left part, the prefix might have whitespace on the
74
+ # the prefix might have whitespace on the
74
75
  # right hand side which is relevant for the position
75
- non_ws_prefix = l[0..pos-1].lstrip
76
- pos = outlines.last.size + non_ws_prefix.size
76
+ pos = outlines.last.size + pos
77
77
  end
78
- outlines.last.concat(l.lstrip)
78
+ outlines.last.concat(l)
79
79
  end
80
80
  end
81
81
  [outlines, pos]
@@ -247,7 +247,15 @@ class Instantiator
247
247
  element.setOrAddGeneric(feature.name, proxy)
248
248
  else
249
249
  begin
250
- element.setOrAddGeneric(feature.name, v.value)
250
+ v_value = v.value
251
+ feature_instance_class = feature.eType.instanceClass
252
+ if feature_instance_class == String && (v_value.is_a?(Float) || v_value.is_a?(Fixnum))
253
+ element.setOrAddGeneric(feature.name, v_value.to_s)
254
+ elsif feature_instance_class == Float && v_value.is_a?(Fixnum)
255
+ element.setOrAddGeneric(feature.name, v_value.to_f)
256
+ else
257
+ element.setOrAddGeneric(feature.name, v_value)
258
+ end
251
259
  rescue StandardError
252
260
  # backward compatibility for RGen versions not supporting BigDecimal
253
261
  if v.value.is_a?(BigDecimal)
@@ -302,9 +310,9 @@ class Instantiator
302
310
  elsif feature.eType.is_a?(RGen::ECore::EEnum)
303
311
  [:identifier, :string]
304
312
  else
305
- expected = { String => [:string, :identifier],
313
+ expected = { String => [:string, :identifier, :integer, :float],
306
314
  Integer => [:integer],
307
- Float => [:float],
315
+ Float => [:float, :integer],
308
316
  RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean],
309
317
  Object => [:string, :identifier, :integer, :float, :boolean]
310
318
  }[feature.eType.instanceClass]
@@ -260,7 +260,7 @@ class Language
260
260
  end
261
261
 
262
262
  def concrete_types(clazz)
263
- ([clazz] + clazz.eAllSubTypes).select{|c| !c.abstract}
263
+ ([clazz] + clazz.eAllSubTypes).select{|c| c.concrete}
264
264
  end
265
265
 
266
266
  def containments_by_target_type(clazz, type)
@@ -299,7 +299,7 @@ class Language
299
299
  @command_by_class = {}
300
300
  @has_command = {}
301
301
  root_epackage.eAllClasses.each do |c|
302
- next if c.abstract
302
+ next unless c.concrete
303
303
  cmd = command_name_provider.call(c)
304
304
  @command_by_class[c.instanceClass] = cmd
305
305
  @has_command[cmd] = true
@@ -307,7 +307,7 @@ class Language
307
307
  @class_by_command[clazz] ||= {}
308
308
  containments(c).collect{|r|
309
309
  [r.eType] + r.eType.eAllSubTypes}.flatten.uniq.each do |t|
310
- next if t.abstract
310
+ next unless t.concrete
311
311
  cmw = command_name_provider.call(t)
312
312
  raise "ambiguous command name #{cmw}" if @class_by_command[clazz][cmw]
313
313
  @class_by_command[clazz][cmw] = t.instanceClass
@@ -315,7 +315,7 @@ class Language
315
315
  end
316
316
  @class_by_command[nil] = {}
317
317
  @root_classes.each do |c|
318
- next if c.abstract
318
+ next unless c.concrete
319
319
  cmw = command_name_provider.call(c)
320
320
  raise "ambiguous command name #{cmw}" if @class_by_command[nil][cmw]
321
321
  @class_by_command[nil][cmw] = c.instanceClass
@@ -323,7 +323,7 @@ class Language
323
323
  end
324
324
 
325
325
  def default_root_classes(root_package)
326
- root_epackage.eAllClasses.select{|c| !c.abstract &&
326
+ root_epackage.eAllClasses.select{|c| c.concrete &&
327
327
  !c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
328
328
  end
329
329
 
@@ -179,7 +179,7 @@ class Serializer
179
179
  result << v.to_s
180
180
  end
181
181
  elsif feature.eType.instanceClass == Object
182
- if v.to_s =~ /^\d+(\.\d+)?$|^\w+$|^true$|^false$/
182
+ if v.to_s =~ /^-?\d+(\.\d+)?$|^\w+$|^true$|^false$/
183
183
  result << v.to_s
184
184
  else
185
185
  result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
data/lib/rtext/service.rb CHANGED
@@ -1,253 +1,264 @@
1
- require 'socket'
2
- require 'rtext/context_builder'
3
- require 'rtext/message_helper'
4
- require 'rtext/link_detector'
5
-
6
- # optimization: garbage collect while service is idle
7
-
8
- module RText
9
-
10
- class Service
11
- include RText::MessageHelper
12
-
13
- PortRangeStart = 9001
14
- PortRangeEnd = 9100
15
-
16
- FlushInterval = 1
17
-
18
- # Creates an RText backend service. Options:
19
- #
20
- # :timeout
21
- # idle time in seconds after which the service will terminate itelf
22
- #
23
- # :logger
24
- # a logger object on which the service will write its logging output
25
- #
26
- # :on_startup:
27
- # a Proc which is called right after the service has started up
28
- # can be used to output version information
29
- #
30
- def initialize(service_provider, options={})
31
- @service_provider = service_provider
32
- @timeout = options[:timeout] || 60
33
- @logger = options[:logger]
34
- @on_startup = options[:on_startup]
35
- end
36
-
37
- def run
38
- server = create_server
39
- puts "RText service, listening on port #{server.addr[1]}"
40
- @on_startup.call if @on_startup
41
- $stdout.flush
42
-
43
- last_access_time = Time.now
44
- last_flush_time = Time.now
45
- @stop_requested = false
46
- sockets = []
47
- request_data = {}
48
- while !@stop_requested
49
- begin
50
- sock = server.accept_nonblock
51
- sock.sync = true
52
- sockets << sock
53
- @logger.info "accepted connection" if @logger
54
- rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, Errno::EWOULDBLOCK
55
- rescue Exception => e
56
- @logger.warn "unexpected exception during socket accept: #{e.class}"
57
- end
58
- sockets.dup.each do |sock|
59
- data = nil
60
- begin
61
- data = sock.read_nonblock(100000)
62
- rescue Errno::EWOULDBLOCK
63
- rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
64
- sock.close
65
- request_data[sock] = nil
66
- sockets.delete(sock)
67
- rescue Exception => e
68
- # catch Exception to make sure we don't crash due to unexpected exceptions
69
- @logger.warn "unexpected exception during socket read: #{e.class}"
70
- sock.close
71
- request_data[sock] = nil
72
- sockets.delete(sock)
73
- end
74
- if data
75
- last_access_time = Time.now
76
- request_data[sock] ||= ""
77
- request_data[sock].concat(data)
78
- while obj = extract_message(request_data[sock])
79
- message_received(sock, obj)
80
- end
81
- end
82
- end
83
- IO.select([server] + sockets, [], [], 1)
84
- if Time.now > last_access_time + @timeout
85
- @logger.info("RText service, stopping now (timeout)") if @logger
86
- break
87
- end
88
- if Time.now > last_flush_time + FlushInterval
89
- $stdout.flush
90
- last_flush_time = Time.now
91
- end
92
- end
93
- end
94
-
95
- def message_received(sock, obj)
96
- if check_request(obj)
97
- request_start = Time.now
98
- @logger.debug("request: "+obj.inspect) if @logger
99
- response = { "type" => "response", "invocation_id" => obj["invocation_id"] }
100
- case obj["command"]
101
- when "load_model"
102
- load_model(sock, obj, response)
103
- when "content_complete"
104
- content_complete(sock, obj, response)
105
- when "link_targets"
106
- link_targets(sock, obj, response)
107
- when "find_elements"
108
- find_elements(sock, obj, response)
109
- when "stop"
110
- @logger.info("RText service, stopping now (stop requested)") if @logger
111
- @stop_requested = true
112
- else
113
- @logger.warn("unknown command #{obj["command"]}") if @logger
114
- response["type"] = "unknown_command_error"
115
- response["command"] = obj["command"]
116
- end
117
- @logger.debug("response: "+truncate_response_for_debug_output(response).inspect) \
118
- if response && @logger
119
- send_response(sock, response)
120
- @logger.info("request complete (#{Time.now-request_start}s)")
121
- end
122
- end
123
-
124
- private
125
-
126
- def check_request(obj)
127
- if obj["type"] != "request"
128
- @logger.warn("received message is not a request") if @logger
129
- false
130
- elsif !obj["invocation_id"].is_a?(Integer)
131
- @logger.warn("invalid invocation id #{obj["invocation_id"]}") if @logger
132
- false
133
- else
134
- true
135
- end
136
- end
137
-
138
- def load_model(sock, request, response)
139
- problems = @service_provider.get_problems(
140
- :on_progress => lambda do |frag, work_done, work_overall|
141
- work_overall = 1 if work_overall < 1
142
- work_done = work_overall if work_done > work_overall
143
- work_done = 0 if work_done < 0
144
- send_response(sock, {
145
- "type" => "progress",
146
- "invocation_id" => request["invocation_id"],
147
- "percentage" => work_done*100/work_overall
148
- })
149
- end)
150
- total = 0
151
- response["problems"] = problems.collect do |fp|
152
- { "file" => fp.file,
153
- "problems" => fp.problems.collect do |p|
154
- total += 1
155
- { "severity" => "error", "line" => p.line, "message" => p.message }
156
- end }
157
- end
158
- response["total_problems"] = total
159
- end
160
-
161
- InsertString = "insert"
162
- DisplayString = "display"
163
-
164
- def content_complete(sock, request, response)
165
- # column numbers start at 1
166
- linepos = request["column"]-1
167
- lines = request["context"]
168
- lang = @service_provider.language
169
- response["options"] = []
170
- return unless lang
171
- context = ContextBuilder.build_context(lang, lines, linepos)
172
- @logger.debug("context element: #{lang.identifier_provider.call(context.element, nil, nil, nil)}") \
173
- if context && context.element && @logger
174
- options = @service_provider.get_completion_options(context)
175
- insert_str = "insert"
176
- display_str = "display"
177
- response["options"] = options.collect do |o|
178
- { insert_str => o.text, display_str => "#{o.text} #{o.extra}" }
179
- end
180
- end
181
-
182
- def link_targets(sock, request, response)
183
- # column numbers start at 1
184
- linepos = request["column"]
185
- lines = request["context"]
186
- lang = @service_provider.language
187
- response["targets"] = []
188
- return unless lang
189
- link_descriptor = RText::LinkDetector.new(lang).detect(lines, linepos)
190
- if link_descriptor
191
- response["begin_column"] = link_descriptor.scol
192
- response["end_column"] = link_descriptor.ecol
193
- targets = []
194
- @service_provider.get_link_targets(link_descriptor).each do |t|
195
- targets << { "file" => t.file, "line" => t.line, "display" => t.display_name }
196
- end
197
- response["targets"] = targets
198
- end
199
- end
200
-
201
- def find_elements(sock, request, response)
202
- pattern = request["search_pattern"]
203
- total = 0
204
- response["elements"] = @service_provider.get_open_element_choices(pattern).collect do |c|
205
- total += 1
206
- { "display" => c.display_name, "file" => c.file, "line" => c.line }
207
- end
208
- response["total_elements"] = total
209
- end
210
-
211
- def send_response(sock, response)
212
- if response
213
- begin
214
- sock.write(serialize_message(response))
215
- sock.flush
216
- # if there is an exception, the next read should shutdown the connection properly
217
- rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
218
- rescue Exception => e
219
- # catch Exception to make sure we don't crash due to unexpected exceptions
220
- @logger.warn "unexpected exception during socket write: #{e.class}"
221
- end
222
- end
223
- end
224
-
225
- def truncate_response_for_debug_output(response_hash)
226
- result = {}
227
- response_hash.each_pair do |k,v|
228
- if v.is_a?(Array) && v.size > 100
229
- result[k] = v[0..99] + ["<truncated>"]
230
- else
231
- result[k] = v
232
- end
233
- end
234
- result
235
- end
236
-
237
- def create_server
238
- port = PortRangeStart
239
- serv = nil
240
- begin
241
- serv = TCPServer.new("127.0.0.1", port)
242
- rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT, Errno::EACCES
243
- port += 1
244
- retry if port <= PortRangeEnd
245
- raise
246
- end
247
- serv
248
- end
249
-
250
- end
251
-
252
- end
253
-
1
+ require 'socket'
2
+ require 'rtext/context_builder'
3
+ require 'rtext/message_helper'
4
+ require 'rtext/link_detector'
5
+
6
+ # optimization: garbage collect while service is idle
7
+
8
+ module RText
9
+
10
+ class Service
11
+ include RText::MessageHelper
12
+
13
+ PortRangeStart = 9001
14
+ PortRangeEnd = 9100
15
+
16
+ FlushInterval = 1
17
+ ProtocolVersion = 1
18
+
19
+ # Creates an RText backend service. Options:
20
+ #
21
+ # :timeout
22
+ # idle time in seconds after which the service will terminate itself
23
+ #
24
+ # :logger
25
+ # a logger object on which the service will write its logging output
26
+ #
27
+ # :on_startup:
28
+ # a Proc which is called right after the service has started up
29
+ # can be used to output version information
30
+ #
31
+ def initialize(service_provider, options={})
32
+ @service_provider = service_provider
33
+ @timeout = options[:timeout] || 60
34
+ @logger = options[:logger]
35
+ @on_startup = options[:on_startup]
36
+ end
37
+
38
+ def run
39
+ server = create_server
40
+ puts "RText service, listening on port #{server.addr[1]}"
41
+ @on_startup.call if @on_startup
42
+ $stdout.flush
43
+
44
+ last_access_time = Time.now
45
+ last_flush_time = Time.now
46
+ @stop_requested = false
47
+ sockets = []
48
+ request_data = {}
49
+ while !@stop_requested
50
+ begin
51
+ sock = server.accept_nonblock
52
+ sock.sync = true
53
+ sockets << sock
54
+ @logger.info "accepted connection" if @logger
55
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, Errno::EWOULDBLOCK
56
+ rescue Exception => e
57
+ @logger.warn "unexpected exception during socket accept: #{e.class}"
58
+ end
59
+ sockets.dup.each do |sock|
60
+ data = nil
61
+ begin
62
+ data = sock.read_nonblock(100000)
63
+ rescue Errno::EWOULDBLOCK
64
+ rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
65
+ sock.close
66
+ request_data[sock] = nil
67
+ sockets.delete(sock)
68
+ rescue Exception => e
69
+ # catch Exception to make sure we don't crash due to unexpected exceptions
70
+ @logger.warn "unexpected exception during socket read: #{e.class}"
71
+ sock.close
72
+ request_data[sock] = nil
73
+ sockets.delete(sock)
74
+ end
75
+ if data
76
+ last_access_time = Time.now
77
+ request_data[sock] ||= ""
78
+ request_data[sock].concat(data)
79
+ while obj = extract_message(request_data[sock])
80
+ message_received(sock, obj)
81
+ end
82
+ end
83
+ end
84
+ IO.select([server] + sockets, [], [], 1)
85
+ if Time.now > last_access_time + @timeout
86
+ @logger.info("RText service, stopping now (timeout)") if @logger
87
+ break
88
+ end
89
+ if Time.now > last_flush_time + FlushInterval
90
+ $stdout.flush
91
+ last_flush_time = Time.now
92
+ end
93
+ end
94
+ end
95
+
96
+ def message_received(sock, obj)
97
+ if check_request(obj)
98
+ request_start = Time.now
99
+ @logger.debug("request: "+obj.inspect) if @logger
100
+ response = { "type" => "response", "invocation_id" => obj["invocation_id"] }
101
+ case obj["command"]
102
+ when "version"
103
+ version(sock, obj, response)
104
+ when "load_model"
105
+ load_model(sock, obj, response)
106
+ when "content_complete"
107
+ content_complete(sock, obj, response)
108
+ when "link_targets"
109
+ link_targets(sock, obj, response)
110
+ when "find_elements"
111
+ find_elements(sock, obj, response)
112
+ when "stop"
113
+ @logger.info("RText service, stopping now (stop requested)") if @logger
114
+ @stop_requested = true
115
+ else
116
+ @logger.warn("unknown command #{obj["command"]}") if @logger
117
+ response["type"] = "unknown_command_error"
118
+ response["command"] = obj["command"]
119
+ end
120
+ @logger.debug("response: "+truncate_response_for_debug_output(response).inspect) \
121
+ if response && @logger
122
+ send_response(sock, response)
123
+ @logger.info("request complete (#{Time.now-request_start}s)")
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def check_request(obj)
130
+ if obj["type"] != "request"
131
+ @logger.warn("received message is not a request") if @logger
132
+ false
133
+ elsif !obj["invocation_id"].is_a?(Integer)
134
+ @logger.warn("invalid invocation id #{obj["invocation_id"]}") if @logger
135
+ false
136
+ else
137
+ true
138
+ end
139
+ end
140
+
141
+ def version(sock, request, response)
142
+ response["version"] = ProtocolVersion
143
+ end
144
+
145
+ def load_model(sock, request, response)
146
+ problems = @service_provider.get_problems(
147
+ :on_progress => lambda do |frag, work_done, work_overall|
148
+ work_overall = 1 if work_overall < 1
149
+ work_done = work_overall if work_done > work_overall
150
+ work_done = 0 if work_done < 0
151
+ send_response(sock, {
152
+ "type" => "progress",
153
+ "invocation_id" => request["invocation_id"],
154
+ "percentage" => work_done*100/work_overall
155
+ })
156
+ end)
157
+ total = 0
158
+ response["problems"] = problems.collect do |fp|
159
+ { "file" => fp.file,
160
+ "problems" => fp.problems.collect do |p|
161
+ total += 1
162
+ { "severity" => "error", "line" => p.line, "message" => p.message }
163
+ end }
164
+ end
165
+ response["total_problems"] = total
166
+ end
167
+
168
+ InsertString = "insert"
169
+ DisplayString = "display"
170
+ DescriptionString = "desc"
171
+
172
+ def content_complete(sock, request, response)
173
+ # column numbers start at 1
174
+ linepos = request["column"]-1
175
+ lines = request["context"]
176
+ version = request["version"].to_i
177
+ lang = @service_provider.language
178
+ response["options"] = []
179
+ return unless lang
180
+ context = ContextBuilder.build_context(lang, lines, linepos)
181
+ @logger.debug("context element: #{lang.identifier_provider.call(context.element, nil, nil, nil)}") \
182
+ if context && context.element && @logger
183
+ if @service_provider.method(:get_completion_options).arity == 1
184
+ options = @service_provider.get_completion_options(context)
185
+ else
186
+ options = @service_provider.get_completion_options(context, version)
187
+ end
188
+ response["options"] = options.collect do |o|
189
+ { InsertString => o.insert, DisplayString => o.display, DescriptionString => o.description }
190
+ end
191
+ end
192
+
193
+ def link_targets(sock, request, response)
194
+ # column numbers start at 1
195
+ linepos = request["column"]
196
+ lines = request["context"]
197
+ lang = @service_provider.language
198
+ response["targets"] = []
199
+ return unless lang
200
+ link_descriptor = RText::LinkDetector.new(lang).detect(lines, linepos)
201
+ if link_descriptor
202
+ response["begin_column"] = link_descriptor.scol
203
+ response["end_column"] = link_descriptor.ecol
204
+ targets = []
205
+ @service_provider.get_link_targets(link_descriptor).each do |t|
206
+ targets << { "file" => t.file, "line" => t.line, "display" => t.display_name }
207
+ end
208
+ response["targets"] = targets
209
+ end
210
+ end
211
+
212
+ def find_elements(sock, request, response)
213
+ pattern = request["search_pattern"]
214
+ total = 0
215
+ response["elements"] = @service_provider.get_open_element_choices(pattern).collect do |c|
216
+ total += 1
217
+ { "display" => c.display_name, "file" => c.file, "line" => c.line }
218
+ end
219
+ response["total_elements"] = total
220
+ end
221
+
222
+ def send_response(sock, response)
223
+ if response
224
+ begin
225
+ sock.write(serialize_message(response))
226
+ sock.flush
227
+ # if there is an exception, the next read should shutdown the connection properly
228
+ rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
229
+ rescue Exception => e
230
+ # catch Exception to make sure we don't crash due to unexpected exceptions
231
+ @logger.warn "unexpected exception during socket write: #{e.class}"
232
+ end
233
+ end
234
+ end
235
+
236
+ def truncate_response_for_debug_output(response_hash)
237
+ result = {}
238
+ response_hash.each_pair do |k,v|
239
+ if v.is_a?(Array) && v.size > 100
240
+ result[k] = v[0..99] + ["<truncated>"]
241
+ else
242
+ result[k] = v
243
+ end
244
+ end
245
+ result
246
+ end
247
+
248
+ def create_server
249
+ port = PortRangeStart
250
+ serv = nil
251
+ begin
252
+ serv = TCPServer.new("127.0.0.1", port)
253
+ rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT, Errno::EACCES
254
+ port += 1
255
+ retry if port <= PortRangeEnd
256
+ raise
257
+ end
258
+ serv
259
+ end
260
+
261
+ end
262
+
263
+ end
264
+