rtext 0.8.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -77,7 +77,7 @@ module Tokenizer
77
77
  str = $'
78
78
  result << Token.new(:boolean, $& == "true", idx, col, col+$&.size-1)
79
79
  col += $&.size
80
- when /\A([a-zA-Z_]\w*)\b(?:\s*:)?/
80
+ when /\A([a-zA-Z_]\w*)\b:?/
81
81
  str = $'
82
82
  if $&[-1] == ?:
83
83
  result << Token.new(:label, $1, idx, col, col+$&.size-1)