rtext 0.5.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|