rtext 0.5.1 → 0.7.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.
- data/CHANGELOG +29 -0
- data/MIT-LICENSE +1 -1
- data/RText_Protocol +102 -51
- data/Rakefile +1 -1
- data/lib/rtext/context_builder.rb +8 -1
- data/lib/rtext/default_completer.rb +163 -0
- data/lib/rtext/default_loader.rb +28 -20
- data/lib/rtext/default_resolver.rb +42 -0
- data/lib/rtext/default_service_provider.rb +49 -21
- data/lib/rtext/frontend/connector.rb +2 -1
- data/lib/rtext/instantiator.rb +38 -16
- data/lib/rtext/json_interface.rb +31 -0
- data/lib/rtext/language.rb +24 -4
- data/lib/rtext/message_helper.rb +35 -25
- data/lib/rtext/parser.rb +27 -45
- data/lib/rtext/serializer.rb +13 -5
- data/lib/rtext/service.rb +27 -18
- data/lib/rtext/tokenizer.rb +18 -2
- data/test/completer_test.rb +35 -18
- data/test/context_builder_test.rb +7 -7
- data/test/instantiator_test.rb +116 -48
- data/test/integration/backend.out +13 -10
- data/test/integration/frontend.log +7664 -0
- data/test/integration/test.rb +6 -0
- data/test/message_helper_test.rb +4 -4
- data/test/serializer_test.rb +101 -3
- data/test/tokenizer_test.rb +33 -1
- metadata +7 -5
- data/lib/rtext/completer.rb +0 -128
data/CHANGELOG
CHANGED
@@ -47,3 +47,32 @@
|
|
47
47
|
|
48
48
|
* Fixed service connection problem when ports are in use by other processes
|
49
49
|
|
50
|
+
=0.5.2
|
51
|
+
|
52
|
+
* Fixed exception in default service provider when trying to follow a reference on an attribute value
|
53
|
+
* Ignore BOM in instantiator
|
54
|
+
|
55
|
+
=0.5.3
|
56
|
+
|
57
|
+
* Fixed completion of enum values which need to be quoted
|
58
|
+
* Added support for BigDecimal
|
59
|
+
|
60
|
+
=0.6.0
|
61
|
+
|
62
|
+
* Changed service provider interface to allow for more customization
|
63
|
+
* Changed Completer into DefaultCompleter to allow for customization
|
64
|
+
* Added labeled_containments language parameter
|
65
|
+
* Made DefaultLoader robust against missing files
|
66
|
+
|
67
|
+
=0.7.0
|
68
|
+
|
69
|
+
* Added DefaultResolver and support for custom resolvers for DefaultLoader and DefaultServiceProvider
|
70
|
+
* Changed instantiator on_progress proc to take a second argument with the number of progress steps
|
71
|
+
* Changed tokenizer to start new tokens immediately after error tokens
|
72
|
+
* Fixed line number in instantiator problem report for multiple childs in one-role
|
73
|
+
* Fixed unit tests for Ruby 2.0
|
74
|
+
* Fixed context builder prefixes for strings
|
75
|
+
* Fixed DefaultLoader to let fragments calculate their elements list by themselves
|
76
|
+
* Minor performance improvements, mainly for instantiator and when passing large RText messages
|
77
|
+
* Improved performance of frontend connector especially for large amounts of data
|
78
|
+
|
data/MIT-LICENSE
CHANGED
data/RText_Protocol
CHANGED
@@ -296,94 +296,145 @@ This information can be used to show the commands in a hierarchical menu, e.g. a
|
|
296
296
|
]
|
297
297
|
}
|
298
298
|
|
299
|
-
=== Custom Command
|
299
|
+
=== Custom Command Parameters
|
300
300
|
|
301
|
-
|
302
|
-
|
303
|
-
the new invocation request. If the command id was returned by a "Costum Commands" request which
|
304
|
-
included context information, the frontend should send the same context information in the new
|
305
|
-
invocation request and all repeated requests (see below). If this information was not present
|
306
|
-
in command list request, it should not be send in the new command invocation.
|
301
|
+
Before a custom command can be invoked, the frontend must check with the backend if parameters
|
302
|
+
are required. This is done by means of the command
|
307
303
|
|
308
304
|
{
|
309
305
|
"type": "request",
|
310
|
-
"command":
|
306
|
+
"command": "check_custom_command_parameters",
|
307
|
+
"command_id": <custom command id>
|
308
|
+
"context": <context lines, optional>,
|
309
|
+
"column": <cursor column, optional>
|
310
|
+
"parameters": [
|
311
|
+
{
|
312
|
+
"name": <parameter name>,
|
313
|
+
"value": <parameter value>
|
314
|
+
}
|
315
|
+
...
|
316
|
+
]
|
311
317
|
}
|
312
318
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
319
|
+
If the command id was returned by a "Costum Commands" request which
|
320
|
+
included context information, the frontend should send the same context information in the new
|
321
|
+
invocation request and all repeated requests (see below). If this information was not sent
|
322
|
+
in command list request (non-context sensitive command), it should neither be send in the new
|
323
|
+
command invocation.
|
318
324
|
|
319
|
-
|
320
|
-
|
321
|
-
Fields may be marked to indicate that their value should be sent to the backend on next invocation.
|
325
|
+
If a command requires parameters, the response will have the field `success` set to false and
|
326
|
+
will include information for building a user dialog.
|
322
327
|
|
323
328
|
The dialogs contain elements of certain types:
|
324
329
|
|
325
|
-
*
|
326
|
-
* text
|
327
|
-
*
|
328
|
-
*
|
330
|
+
* label: text presented to the user
|
331
|
+
* text: text input field
|
332
|
+
* table: table view/input
|
333
|
+
* hidden: hidden field used to hold state information
|
334
|
+
|
335
|
+
The frontend should keep repeating requests as long as the field ``success`` is set to ``false``.
|
329
336
|
|
330
|
-
|
337
|
+
Note that the backend doesn't hold any state, so that commands can easily be canceled by just
|
338
|
+
not making any additional requests (and not actually invoking the command).
|
331
339
|
|
332
|
-
|
333
|
-
|
334
|
-
* editable: if the user can edit the field [Boolean]
|
335
|
-
* visible: if the field is visible, there may be hidden fields for internal purpose [Boolean]
|
336
|
-
* return: if the value should be sent back with the next request [Boolean]
|
337
|
-
* error: an error description, if present the field should be marked to have an error [String]
|
338
|
-
* value: the preset value of the field, type is field type specific
|
340
|
+
If the response contains step information (number of steps, current step), the the frontend
|
341
|
+
should display buttons for moving forward and backward in a wizard.
|
339
342
|
|
340
|
-
|
343
|
+
When the frontend receives dialog information, it should display the dialog filled with the
|
344
|
+
widgets as specified and with widget content set up properly (text fields preset with strings,
|
345
|
+
rows selected in table, etc).
|
346
|
+
|
347
|
+
When the user has finished the dialog, the frontend should send all input information back to
|
348
|
+
the backend as name/value pairs. This includes:
|
349
|
+
|
350
|
+
* hidden fields
|
351
|
+
* text fields unless disabled
|
352
|
+
* table fields unless disabled
|
341
353
|
|
342
354
|
{
|
343
355
|
"type": "response",
|
344
|
-
"
|
356
|
+
"success": <true|false, true if command is finshed>,
|
357
|
+
"num_steps", <number of steps of this (wizard) command, optional>,
|
358
|
+
"current_step", <current wizard step, optional>,
|
345
359
|
"dialog": {
|
346
360
|
"title": <dialog title>,
|
347
|
-
"desc": <description what to do>,
|
348
361
|
"elements": [
|
349
362
|
{
|
350
|
-
"type": "
|
363
|
+
"type": "lable",
|
364
|
+
"text": <text to display>
|
365
|
+
},
|
366
|
+
{
|
367
|
+
"type": "hidden",
|
351
368
|
"name": <parameter name>,
|
352
|
-
"value": <
|
353
|
-
"error": <error text>
|
369
|
+
"value": <value of hidden filed [string]>
|
354
370
|
},
|
355
371
|
{
|
356
|
-
"type": "
|
357
|
-
"
|
358
|
-
"
|
359
|
-
"choices": [
|
360
|
-
"display": <display name>,
|
361
|
-
"id": <identifier to be sent back>,
|
362
|
-
],
|
363
|
-
"value": [ <selected choice>, <selected choice>, ...],
|
372
|
+
"type": "text",
|
373
|
+
"name": <parameter name>,
|
374
|
+
"value": <preset value>,
|
364
375
|
"error": <error text>
|
376
|
+
"description: <discription of parameter, optional>
|
377
|
+
"disabled:" <true|false, if set to false the input widget can't be changed>
|
365
378
|
},
|
366
379
|
{
|
367
|
-
"type": "
|
380
|
+
"type": "table",
|
381
|
+
"name": <parameter name>,
|
368
382
|
"num_min": <min number of elements to choose, default: 1>,
|
369
|
-
"num_max": <max mumber of elements to choose, default: 1>,
|
370
|
-
"
|
383
|
+
"num_max": <max mumber of elements to choose, or * for unlimited, default: 1>,
|
384
|
+
"columns": [
|
385
|
+
{
|
386
|
+
"name": <column name>,
|
387
|
+
"description: <discription of parameter, optional>
|
388
|
+
}
|
389
|
+
...
|
390
|
+
],
|
391
|
+
"rows": [
|
371
392
|
{
|
372
|
-
"display": <display name>,
|
373
393
|
"id": <identifier to be sent back>,
|
374
|
-
"
|
375
|
-
|
376
|
-
|
394
|
+
"values" [
|
395
|
+
{
|
396
|
+
"display": <display name>,
|
397
|
+
"file": <fully qualfied file name, optional>,
|
398
|
+
"line": <line number, optional>,
|
399
|
+
}
|
400
|
+
...
|
401
|
+
]
|
377
402
|
}
|
378
403
|
...
|
379
404
|
],
|
380
|
-
"
|
381
|
-
"error": <error text
|
405
|
+
"selected": [ <row id>, <row id>, ...],
|
406
|
+
"error": <error text>,
|
407
|
+
"description: <discription of parameter, optional>
|
408
|
+
"disabled:" <true|false, if set to false, no selection can be made>
|
382
409
|
}
|
383
410
|
]
|
384
411
|
}
|
385
412
|
}
|
386
413
|
|
414
|
+
=== Custom Command Invocation
|
415
|
+
|
416
|
+
Custom commands are invoked using the command id returned by the "Custom Commands" request.
|
417
|
+
Before a command can be invoked, the parameters must be figured out by means of the "Check
|
418
|
+
Custom Command Parameters" command. Once figured out, the parameters will be sent with the
|
419
|
+
command invocation as a name/value list.
|
420
|
+
|
421
|
+
The context information should be sent as with the "Check Custom Command Parameters" command.
|
422
|
+
|
423
|
+
{
|
424
|
+
"type": "request",
|
425
|
+
"command": "invoke_custom_command",
|
426
|
+
"command_id": <custom command id>
|
427
|
+
"context": <context lines, optional>,
|
428
|
+
"column": <cursor column, optional>
|
429
|
+
"parameters": [
|
430
|
+
{
|
431
|
+
"name": <parameter name>,
|
432
|
+
"value": <parameter value>
|
433
|
+
}
|
434
|
+
...
|
435
|
+
]
|
436
|
+
}
|
437
|
+
|
387
438
|
=== Stop Service
|
388
439
|
|
389
440
|
This command is normally invoked when the frontend terminates or otherwise needs to terminate
|
data/Rakefile
CHANGED
@@ -121,6 +121,7 @@ module ContextBuilder
|
|
121
121
|
missing_comma = false
|
122
122
|
unlabled_index = 0
|
123
123
|
tokens[1..-1].each do |token|
|
124
|
+
break if token.kind == :error
|
124
125
|
if token.kind == "["
|
125
126
|
in_array = true
|
126
127
|
elsif token.kind == "]"
|
@@ -151,7 +152,13 @@ module ContextBuilder
|
|
151
152
|
end
|
152
153
|
if [:error, :string, :integer, :float, :boolean, :identifier, :reference].
|
153
154
|
include?(tokens.last.kind) && line !~ /\s$/
|
154
|
-
|
155
|
+
last_error = tokens.rindex{|t| t.kind == :error && t.value == '"'}
|
156
|
+
last_string = tokens.rindex{|t| t.kind == :string}
|
157
|
+
if last_error && (!last_string || last_error > last_string)
|
158
|
+
prefix = line[tokens[last_error].scol-1..-1]
|
159
|
+
else
|
160
|
+
prefix = line[tokens.last.scol-1..-1]
|
161
|
+
end
|
155
162
|
else
|
156
163
|
prefix = ""
|
157
164
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rgen/ecore/ecore_ext'
|
2
|
+
|
3
|
+
module RText
|
4
|
+
|
5
|
+
class DefaultCompleter
|
6
|
+
|
7
|
+
CompletionOption = Struct.new(:text, :extra)
|
8
|
+
|
9
|
+
# Creates a completer for RText::Language +language+.
|
10
|
+
#
|
11
|
+
def initialize(language)
|
12
|
+
@lang = language
|
13
|
+
end
|
14
|
+
|
15
|
+
# Provides completion options
|
16
|
+
#
|
17
|
+
def complete(context)
|
18
|
+
clazz = context && context.element && context.element.class.ecore
|
19
|
+
if clazz
|
20
|
+
if context.in_block
|
21
|
+
block_options(context, clazz)
|
22
|
+
elsif !context.problem
|
23
|
+
result = []
|
24
|
+
if context.feature
|
25
|
+
add_value_options(context, result)
|
26
|
+
end
|
27
|
+
if !context.after_label
|
28
|
+
add_label_options(context, clazz, result)
|
29
|
+
end
|
30
|
+
result
|
31
|
+
else
|
32
|
+
# missing comma, after curly brace, etc.
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
elsif context
|
36
|
+
root_options
|
37
|
+
else
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def block_options(context, clazz)
|
43
|
+
types = []
|
44
|
+
labled_refs = []
|
45
|
+
if context.feature
|
46
|
+
if context.feature.is_a?(RGen::ECore::EReference) && context.feature.containment
|
47
|
+
types = @lang.concrete_types(context.feature.eType)
|
48
|
+
else
|
49
|
+
# invalid, ignore
|
50
|
+
end
|
51
|
+
else
|
52
|
+
# all target types which don't need a label
|
53
|
+
# and all lables which are needed by a potential target type
|
54
|
+
@lang.containments(clazz).each do |r|
|
55
|
+
([r.eType] + r.eType.eAllSubTypes).select{|t| !t.abstract}.each do |t|
|
56
|
+
if @lang.labeled_containment?(clazz, r) || @lang.containments_by_target_type(clazz, t).size > 1
|
57
|
+
labled_refs << r
|
58
|
+
else
|
59
|
+
types << t
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
types.uniq.
|
65
|
+
sort{|a,b| a.name <=> b.name}.collect do |c|
|
66
|
+
class_completion_option(c)
|
67
|
+
end +
|
68
|
+
labled_refs.uniq.collect do |r|
|
69
|
+
CompletionOption.new("#{r.name}:", "<#{r.eType.name}>")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_value_options(context, result)
|
74
|
+
if context.feature.is_a?(RGen::ECore::EAttribute) || !context.feature.containment
|
75
|
+
if context.feature.is_a?(RGen::ECore::EReference)
|
76
|
+
result.concat(reference_options(context))
|
77
|
+
elsif context.feature.eType.is_a?(RGen::ECore::EEnum)
|
78
|
+
result.concat(enum_options(context))
|
79
|
+
elsif context.feature.eType.instanceClass == String
|
80
|
+
result.concat(string_options(context))
|
81
|
+
elsif context.feature.eType.instanceClass == Integer
|
82
|
+
result.concat(integer_options(context))
|
83
|
+
elsif context.feature.eType.instanceClass == Float
|
84
|
+
result.concat(float_options(context))
|
85
|
+
elsif context.feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
|
86
|
+
result.concat(boolean_options(context))
|
87
|
+
else
|
88
|
+
# no options
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# containment reference, ignore
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_label_options(context, clazz, result)
|
96
|
+
result.concat(@lang.labled_arguments(clazz).
|
97
|
+
select{|f|
|
98
|
+
!context.element.eIsSet(f.name)}.collect do |f|
|
99
|
+
CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
|
100
|
+
end )
|
101
|
+
end
|
102
|
+
|
103
|
+
def root_options
|
104
|
+
@lang.root_classes.
|
105
|
+
sort{|a,b| a.name <=> b.name}.collect do |c|
|
106
|
+
class_completion_option(c)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def reference_options(context)
|
111
|
+
[]
|
112
|
+
end
|
113
|
+
|
114
|
+
def enum_options(context)
|
115
|
+
context.feature.eType.eLiterals.collect do |l|
|
116
|
+
lname = l.name
|
117
|
+
if lname =~ /^\d|\W/ || lname == "true" || lname == "false"
|
118
|
+
lname = "\"#{lname.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
|
119
|
+
gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
|
120
|
+
end
|
121
|
+
CompletionOption.new("#{lname}", "<#{context.feature.eType.name}>")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def string_options(context)
|
126
|
+
if @lang.unquoted?(context.feature)
|
127
|
+
[ CompletionOption.new("#{context.feature.name.gsub(/\W/,"")}", value_description(context)) ]
|
128
|
+
else
|
129
|
+
[ CompletionOption.new("\"\"", value_description(context)) ]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def integer_options(context)
|
134
|
+
(0..0).collect{|i| CompletionOption.new("#{i}", value_description(context)) }
|
135
|
+
end
|
136
|
+
|
137
|
+
def float_options(context)
|
138
|
+
(0..0).collect{|i| CompletionOption.new("#{i}.0", value_description(context)) }
|
139
|
+
end
|
140
|
+
|
141
|
+
def boolean_options(context)
|
142
|
+
[true, false].collect{|b| CompletionOption.new("#{b}", value_description(context)) }
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def value_description(context)
|
148
|
+
if context.after_label
|
149
|
+
"<#{context.feature.eType.name}>"
|
150
|
+
else
|
151
|
+
"[#{context.feature.name}] <#{context.feature.eType.name}>"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def class_completion_option(eclass)
|
156
|
+
uargs = @lang.unlabled_arguments(eclass).collect{|a| "<#{a.name}>"}.join(", ")
|
157
|
+
CompletionOption.new(@lang.command_by_class(eclass.instanceClass), uargs)
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
data/lib/rtext/default_loader.rb
CHANGED
@@ -2,6 +2,7 @@ require 'rgen/environment'
|
|
2
2
|
require 'rgen/util/file_change_detector'
|
3
3
|
require 'rgen/fragment/model_fragment'
|
4
4
|
require 'rtext/instantiator'
|
5
|
+
require 'rtext/default_resolver'
|
5
6
|
|
6
7
|
module RText
|
7
8
|
|
@@ -33,6 +34,10 @@ class DefaultLoader
|
|
33
34
|
# if set to true, don't reload fragments which have parse errors
|
34
35
|
# instead keep the existing fragment but attach the new problem list
|
35
36
|
#
|
37
|
+
# :resolver
|
38
|
+
# a reference resolver responding to the methods provided by DefaultResolver
|
39
|
+
# default: DefaultResolver
|
40
|
+
#
|
36
41
|
def initialize(language, fragmented_model, options={})
|
37
42
|
@lang = language
|
38
43
|
@model = fragmented_model
|
@@ -48,6 +53,7 @@ class DefaultLoader
|
|
48
53
|
pattern = options[:pattern]
|
49
54
|
@file_provider = options[:file_provider] || proc { Dir.glob(pattern) }
|
50
55
|
@dont_reload_with_errors = options[:dont_reload_with_errors]
|
56
|
+
@resolver = options[:resolver] || DefaultResolver.new(language)
|
51
57
|
end
|
52
58
|
|
53
59
|
# Loads or reloads model fragments from files using the file patterns or file provider
|
@@ -83,9 +89,7 @@ class DefaultLoader
|
|
83
89
|
@files_added.each {|f| file_added(f)}
|
84
90
|
@files_changed.each {|f| file_changed(f)}
|
85
91
|
@files_removed.each {|f| file_removed(f)}
|
86
|
-
@
|
87
|
-
@model.resolve(:fragment_provider => method(:fragment_provider),
|
88
|
-
:use_target_type => @lang.per_type_identifier)
|
92
|
+
@resolver.resolve_model(@model)
|
89
93
|
end
|
90
94
|
|
91
95
|
private
|
@@ -109,8 +113,8 @@ class DefaultLoader
|
|
109
113
|
@work_last_sent = @work_done
|
110
114
|
end
|
111
115
|
|
112
|
-
def instantiator_progress(frag)
|
113
|
-
@work_done +=
|
116
|
+
def instantiator_progress(frag, steps)
|
117
|
+
@work_done += steps
|
114
118
|
if @work_done > @work_last_sent + 100
|
115
119
|
@on_progress_proc.call(frag, @work_done, @work_overall)
|
116
120
|
@work_last_sent = @work_done
|
@@ -219,27 +223,31 @@ class DefaultLoader
|
|
219
223
|
problems = []
|
220
224
|
root_elements = []
|
221
225
|
inst = RText::Instantiator.new(@lang)
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
226
|
+
begin
|
227
|
+
File.open(fragment.location, "rb") do |f|
|
228
|
+
inst.instantiate(f.read,
|
229
|
+
:env => env,
|
230
|
+
:unresolved_refs => urefs,
|
231
|
+
:problems => problems,
|
232
|
+
:root_elements => root_elements,
|
233
|
+
:fragment_ref => fragment.fragment_ref,
|
234
|
+
:file_name => fragment.location,
|
235
|
+
:on_progress => lambda do |steps|
|
236
|
+
@progress_monitor.instantiator_progress(fragment, steps)
|
237
|
+
end)
|
238
|
+
end
|
239
|
+
rescue Errno::ENOENT
|
240
|
+
# missing file, treat as empty
|
233
241
|
end
|
234
242
|
# data might have been created during instantiation (e.g. comment or annotation handler)
|
235
243
|
fragment.data ||= {}
|
236
244
|
fragment.data[:problems] = problems
|
245
|
+
# let the fragment calculate the elements, elements might have been added during
|
246
|
+
# instantiation (e.g. comment or annotation handler)
|
237
247
|
fragment.set_root_elements(root_elements,
|
238
|
-
:unresolved_refs => urefs
|
239
|
-
:elements => env.elements)
|
248
|
+
:unresolved_refs => urefs)
|
240
249
|
fragment.build_index
|
241
|
-
@
|
242
|
-
fragment.resolve_local(:use_target_type => @lang.per_type_identifier)
|
250
|
+
@resolver.resolve_fragment(fragment)
|
243
251
|
end
|
244
252
|
|
245
253
|
end
|