rtext 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +10 -2
- data/Rakefile +1 -1
- data/lib/rtext/completer.rb +84 -111
- data/lib/rtext/context_builder.rb +188 -0
- data/lib/rtext/default_loader.rb +8 -0
- data/lib/rtext/default_service_provider.rb +21 -18
- data/lib/rtext/instantiator.rb +28 -9
- data/lib/rtext/language.rb +51 -21
- data/lib/rtext/parser.rb +6 -4
- data/lib/rtext/serializer.rb +3 -2
- data/lib/rtext/service.rb +7 -9
- data/test/completer_test.rb +332 -0
- data/test/context_builder_test.rb +360 -0
- data/test/instantiator_test.rb +95 -13
- data/test/rtext_test.rb +2 -0
- data/test/serializer_test.rb +9 -8
- metadata +8 -11
- data/lib/rtext/context_element_builder.rb +0 -112
@@ -2,11 +2,12 @@ module RText
|
|
2
2
|
|
3
3
|
class DefaultServiceProvider
|
4
4
|
|
5
|
-
def initialize(language, fragmented_model, model_loader)
|
5
|
+
def initialize(language, fragmented_model, model_loader, options={})
|
6
6
|
@lang = language
|
7
7
|
@model = fragmented_model
|
8
8
|
@loader = model_loader
|
9
9
|
@element_name_index = nil
|
10
|
+
@result_limit = options[:result_limit]
|
10
11
|
@model.add_fragment_change_listener(proc {|fragment, kind|
|
11
12
|
@element_name_index = nil
|
12
13
|
})
|
@@ -25,7 +26,7 @@ class DefaultServiceProvider
|
|
25
26
|
targets = @model.index.values.flatten.select{|e| e.is_a?(clazz)}
|
26
27
|
end
|
27
28
|
targets.collect{|t|
|
28
|
-
ident = @lang.identifier_provider.call(t, context)
|
29
|
+
ident = @lang.identifier_provider.call(t, context.element)
|
29
30
|
if ident
|
30
31
|
ReferenceCompletionOption.new(ident, t.class.ecore.name)
|
31
32
|
else
|
@@ -35,20 +36,13 @@ class DefaultServiceProvider
|
|
35
36
|
end
|
36
37
|
|
37
38
|
ReferenceTarget = Struct.new(:file, :line, :display_name)
|
38
|
-
def get_reference_targets(identifier, context
|
39
|
+
def get_reference_targets(identifier, context)
|
39
40
|
result = []
|
40
|
-
identifier = @lang.qualify_reference(identifier, context)
|
41
|
+
identifier = @lang.qualify_reference(identifier, context.element)
|
41
42
|
targets = @model.index[identifier]
|
42
43
|
if targets && @lang.per_type_identifier
|
43
|
-
|
44
|
-
|
45
|
-
if linestart =~ /\s*(\w+)\s+(?:[^,]+,)*\s*(\w+):\s*(\S*)$/
|
46
|
-
command, fn, prefix = $1, $2, $3
|
47
|
-
clazz = @lang.class_by_command(command)
|
48
|
-
feature = clazz && @lang.non_containments(clazz.ecore).find{|f| f.name == fn}
|
49
|
-
if feature
|
50
|
-
targets = targets.select{|t| t.is_a?(feature.eType.instanceClass)}
|
51
|
-
end
|
44
|
+
if context.feature
|
45
|
+
targets = targets.select{|t| t.is_a?(context.feature.eType.instanceClass)}
|
52
46
|
end
|
53
47
|
end
|
54
48
|
targets && targets.each do |t|
|
@@ -62,9 +56,9 @@ class DefaultServiceProvider
|
|
62
56
|
|
63
57
|
def get_referencing_elements(identifier, context)
|
64
58
|
result = []
|
65
|
-
targets = @model.index[@lang.identifier_provider.call(context, nil)]
|
59
|
+
targets = @model.index[@lang.identifier_provider.call(context.element, nil)]
|
66
60
|
if targets && @lang.per_type_identifier
|
67
|
-
targets = targets.select{|t| t.class == context.class}
|
61
|
+
targets = targets.select{|t| t.class == context.element.class}
|
68
62
|
end
|
69
63
|
if targets && targets.size == 1
|
70
64
|
target = targets.first
|
@@ -114,8 +108,9 @@ class DefaultServiceProvider
|
|
114
108
|
result = []
|
115
109
|
return result unless pattern
|
116
110
|
sub_index = element_name_index[pattern[0..0].downcase]
|
111
|
+
truncate_result = false
|
117
112
|
sub_index && sub_index.each_pair do |ident, elements|
|
118
|
-
if ident.split(/\W/).last.downcase.index(pattern.downcase) == 0
|
113
|
+
if !truncate_result && ident.split(/\W/).last.downcase.index(pattern.downcase) == 0
|
119
114
|
elements.each do |e|
|
120
115
|
if @lang.fragment_ref(e)
|
121
116
|
non_word_index = ident.rindex(/\W/)
|
@@ -129,12 +124,20 @@ class DefaultServiceProvider
|
|
129
124
|
display_name = "#{name} [#{e.class.ecore.name}]"
|
130
125
|
display_name += " - #{scope}" if scope.size > 0
|
131
126
|
path = File.expand_path(@lang.fragment_ref(e).fragment.location)
|
132
|
-
|
127
|
+
if !@result_limit || result.size < @result_limit
|
128
|
+
result << OpenElementChoice.new(display_name, path, @lang.line_number(e))
|
129
|
+
else
|
130
|
+
truncate_result = true
|
131
|
+
end
|
133
132
|
end
|
134
133
|
end
|
135
134
|
end
|
136
135
|
end
|
137
|
-
result.sort{|a,b| a.display_name <=> b.display_name}
|
136
|
+
result = result.sort{|a,b| a.display_name <=> b.display_name}
|
137
|
+
if truncate_result
|
138
|
+
result << OpenElementChoice.new("--- result truncated, showing first #{@result_limit} entries ---", "/", 1)
|
139
|
+
end
|
140
|
+
result
|
138
141
|
end
|
139
142
|
|
140
143
|
def element_name_index
|
data/lib/rtext/instantiator.rb
CHANGED
@@ -44,16 +44,26 @@ class Instantiator
|
|
44
44
|
@root_elements = options[:root_elements] || []
|
45
45
|
@file_name = options[:file_name]
|
46
46
|
@fragment_ref = options[:fragment_ref]
|
47
|
+
@context_class_stack = []
|
47
48
|
parser = Parser.new(@lang.reference_regexp)
|
48
49
|
begin
|
49
50
|
@root_elements.clear
|
50
|
-
parser.parse(str
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
parser.parse(str,
|
52
|
+
:descent_visitor => lambda do |command|
|
53
|
+
clazz = @lang.class_by_command(command.value, @context_class_stack.last)
|
54
|
+
# in case no class is found, nil will be pushed, this will case the next command
|
55
|
+
# lookup to act as if called from toplevel
|
56
|
+
@context_class_stack.push(clazz)
|
57
|
+
end,
|
58
|
+
:ascent_visitor => lambda do |*args|
|
59
|
+
if args[0]
|
60
|
+
element =create_element(*args)
|
61
|
+
@context_class_stack.pop
|
62
|
+
element
|
63
|
+
else
|
64
|
+
unassociated_comments(args[3])
|
65
|
+
end
|
66
|
+
end)
|
57
67
|
rescue Parser::Error => e
|
58
68
|
problem(e.message, e.line)
|
59
69
|
@unresolved_refs.clear if @unresolved_refs
|
@@ -75,10 +85,18 @@ class Instantiator
|
|
75
85
|
end
|
76
86
|
|
77
87
|
def create_element(command, arg_list, element_list, comments, is_root)
|
78
|
-
clazz = @
|
79
|
-
if
|
88
|
+
clazz = @context_class_stack.last
|
89
|
+
if !@lang.has_command(command.value)
|
80
90
|
problem("Unknown command '#{command.value}'", command.line)
|
81
91
|
return
|
92
|
+
elsif !clazz
|
93
|
+
if is_root
|
94
|
+
problem("Command '#{command.value}' can not be used on root level", command.line)
|
95
|
+
return
|
96
|
+
else
|
97
|
+
problem("Command '#{command.value}' can not be used in this context", command.line)
|
98
|
+
return
|
99
|
+
end
|
82
100
|
end
|
83
101
|
if clazz.ecore.abstract
|
84
102
|
problem("Unknown command '#{command.value}' (metaclass is abstract)", command.line)
|
@@ -153,6 +171,7 @@ class Instantiator
|
|
153
171
|
return if child.nil?
|
154
172
|
feature = @lang.containments_by_target_type(element.class.ecore, child.class.ecore)
|
155
173
|
if feature.size == 0
|
174
|
+
# this should never happen since the scope of an element is already checked when it's created
|
156
175
|
problem("This kind of element can not be contained here", line_number(child))
|
157
176
|
return
|
158
177
|
end
|
data/lib/rtext/language.rb
CHANGED
@@ -35,11 +35,6 @@ class Language
|
|
35
35
|
# (in sprintf syntax) which will be used by the serializer for integers and floats.
|
36
36
|
# default: if not present or the proc returns nil, then #to_s is used
|
37
37
|
#
|
38
|
-
# :short_class_names
|
39
|
-
# if true, the metamodel is searched for classes by unqualified class name recursively
|
40
|
-
# if false, classes can only be found in the root package, not in subpackages
|
41
|
-
# default: true
|
42
|
-
#
|
43
38
|
# :reference_regexp
|
44
39
|
# a Regexp which is used by the tokenizer for identifying references
|
45
40
|
# it must only match at the beginning of a string, i.e. it should start with \A
|
@@ -75,6 +70,10 @@ class Language
|
|
75
70
|
# default: no reference qualifier, i.e. all identifiers returned by the identifier provider
|
76
71
|
# must be globally unique
|
77
72
|
#
|
73
|
+
# :root_classes
|
74
|
+
# an Array of EClass objects representing classes which can be used on root level
|
75
|
+
# default: all classes which can't be contained by any class
|
76
|
+
#
|
78
77
|
# :line_number_attribute
|
79
78
|
# the name of the attribute which will be used to associate the line number with a model element
|
80
79
|
# default: no line number
|
@@ -87,9 +86,10 @@ class Language
|
|
87
86
|
# the name of the attribute which will be used to associate a model fragment with a model element
|
88
87
|
#
|
89
88
|
# :comment_handler
|
90
|
-
# a Proc which will be invoked when a new element has been instantiated. receives
|
91
|
-
#
|
92
|
-
#
|
89
|
+
# a Proc which will be invoked when a new element has been instantiated. receives
|
90
|
+
# the comment as a string, the comment kind (one of [:above, :eol, :unassociated]), the
|
91
|
+
# element and the environment to which the element has been added to.
|
92
|
+
# the environment may be nil. it should add the comment to the element and
|
93
93
|
# return true. if the element can take no comment, it should return false.
|
94
94
|
# default: no handling of comments
|
95
95
|
#
|
@@ -113,18 +113,9 @@ class Language
|
|
113
113
|
@unlabled_arguments = options[:unlabled_arguments]
|
114
114
|
@unquoted_arguments = options[:unquoted_arguments]
|
115
115
|
@argument_format_provider = options[:argument_format_provider]
|
116
|
-
@
|
116
|
+
@root_classes = options[:root_classes] || default_root_classes(root_epackage)
|
117
117
|
command_name_provider = options[:command_name_provider] || proc{|c| c.name}
|
118
|
-
(
|
119
|
-
root_epackage.eAllClasses : root_epackage.eClasses).each do |c|
|
120
|
-
next if c.abstract
|
121
|
-
command_name = command_name_provider.call(c)
|
122
|
-
raise "ambiguous command name #{command_name}" if @class_by_command[command_name]
|
123
|
-
@class_by_command[command_name] = c.instanceClass
|
124
|
-
end
|
125
|
-
# there can't be multiple commands for the same class as the command name provider
|
126
|
-
# can only return one command per class
|
127
|
-
@command_by_class = @class_by_command.invert
|
118
|
+
setup_commands(root_epackage, command_name_provider)
|
128
119
|
@reference_regexp = options[:reference_regexp] || /\A\w*(\/\w*)+/
|
129
120
|
@identifier_provider = options[:identifier_provider] ||
|
130
121
|
proc { |element, context|
|
@@ -142,6 +133,7 @@ class Language
|
|
142
133
|
end
|
143
134
|
|
144
135
|
attr_reader :root_epackage
|
136
|
+
attr_reader :root_classes
|
145
137
|
attr_reader :reference_regexp
|
146
138
|
attr_reader :identifier_provider
|
147
139
|
attr_reader :line_number_attribute
|
@@ -152,8 +144,13 @@ class Language
|
|
152
144
|
attr_reader :indent_string
|
153
145
|
attr_reader :per_type_identifier
|
154
146
|
|
155
|
-
def class_by_command(command)
|
156
|
-
@class_by_command[
|
147
|
+
def class_by_command(command, context_class)
|
148
|
+
map = @class_by_command[context_class]
|
149
|
+
map && map[command]
|
150
|
+
end
|
151
|
+
|
152
|
+
def has_command(command)
|
153
|
+
@has_command[command]
|
157
154
|
end
|
158
155
|
|
159
156
|
def command_by_class(clazz)
|
@@ -225,6 +222,39 @@ class Language
|
|
225
222
|
|
226
223
|
private
|
227
224
|
|
225
|
+
def setup_commands(root_epackage, command_name_provider)
|
226
|
+
@class_by_command = {}
|
227
|
+
@command_by_class = {}
|
228
|
+
@has_command = {}
|
229
|
+
root_epackage.eAllClasses.each do |c|
|
230
|
+
next if c.abstract
|
231
|
+
cmd = command_name_provider.call(c)
|
232
|
+
@command_by_class[c.instanceClass] = cmd
|
233
|
+
@has_command[cmd] = true
|
234
|
+
clazz = c.instanceClass
|
235
|
+
@class_by_command[clazz] ||= {}
|
236
|
+
c.eAllReferences.select{|r| r.containment}.collect{|r|
|
237
|
+
[r.eType] + r.eType.eAllSubTypes}.flatten.uniq.each do |t|
|
238
|
+
next if t.abstract
|
239
|
+
cmw = command_name_provider.call(t)
|
240
|
+
raise "ambiguous command name #{cmw}" if @class_by_command[clazz][cmw]
|
241
|
+
@class_by_command[clazz][cmw] = t.instanceClass
|
242
|
+
end
|
243
|
+
end
|
244
|
+
@class_by_command[nil] = {}
|
245
|
+
@root_classes.each do |c|
|
246
|
+
next if c.abstract
|
247
|
+
cmw = command_name_provider.call(c)
|
248
|
+
raise "ambiguous command name #{cmw}" if @class_by_command[nil][cmw]
|
249
|
+
@class_by_command[nil][cmw] = c.instanceClass
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def default_root_classes(root_package)
|
254
|
+
root_epackage.eAllClasses.select{|c| !c.abstract &&
|
255
|
+
!c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
|
256
|
+
end
|
257
|
+
|
228
258
|
def features(clazz)
|
229
259
|
@feature_provider.call(clazz)
|
230
260
|
end
|
data/lib/rtext/parser.rb
CHANGED
@@ -6,8 +6,9 @@ class Parser
|
|
6
6
|
@reference_regexp = reference_regexp
|
7
7
|
end
|
8
8
|
|
9
|
-
def parse(str,
|
10
|
-
@
|
9
|
+
def parse(str, options)
|
10
|
+
@dsc_visitor = options[:descent_visitor]
|
11
|
+
@asc_visitor = options[:ascent_visitor]
|
11
12
|
@tokens = tokenize(str, @reference_regexp)
|
12
13
|
@last_line = @tokens.last && @tokens.last.line
|
13
14
|
while next_token
|
@@ -22,6 +23,7 @@ class Parser
|
|
22
23
|
if (next_token && next_token == :identifier) || !allow_unassociated_comment
|
23
24
|
comments << [ comment, :above] if comment
|
24
25
|
command = consume(:identifier)
|
26
|
+
@dsc_visitor.call(command)
|
25
27
|
arg_list = []
|
26
28
|
parse_argument_list(arg_list)
|
27
29
|
element_list = []
|
@@ -31,11 +33,11 @@ class Parser
|
|
31
33
|
eol_comment = parse_eol_comment
|
32
34
|
comments << [ eol_comment, :eol ] if eol_comment
|
33
35
|
consume(:newline)
|
34
|
-
@
|
36
|
+
@asc_visitor.call(command, arg_list, element_list, comments, is_root)
|
35
37
|
elsif comment
|
36
38
|
# if there is no statement, the comment is non-optional
|
37
39
|
comments << [ comment, :unassociated ]
|
38
|
-
@
|
40
|
+
@asc_visitor.call(nil, nil, nil, comments, nil)
|
39
41
|
nil
|
40
42
|
else
|
41
43
|
# die expecting an identifier (next token is not an identifier)
|
data/lib/rtext/serializer.rb
CHANGED
@@ -80,7 +80,8 @@ class Serializer
|
|
80
80
|
@lang.containments(clazz).each do |f|
|
81
81
|
childs = contained_elements[f]
|
82
82
|
if childs.size > 0
|
83
|
-
|
83
|
+
child_classes = childs.collect{|c| c.class.ecore}.uniq
|
84
|
+
if child_classes.any?{|c| @lang.containments_by_target_type(element.class.ecore, c).size > 1}
|
84
85
|
if childs.size > 1
|
85
86
|
write("#{f.name}: [")
|
86
87
|
iinc
|
@@ -132,7 +133,7 @@ class Serializer
|
|
132
133
|
result << v.to_s
|
133
134
|
end
|
134
135
|
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
135
|
-
if v.to_s =~
|
136
|
+
if v.to_s =~ /\W/
|
136
137
|
result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
|
137
138
|
gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
|
138
139
|
else
|
data/lib/rtext/service.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'rtext/completer'
|
3
|
-
require 'rtext/
|
3
|
+
require 'rtext/context_builder'
|
4
4
|
|
5
5
|
module RText
|
6
6
|
|
@@ -119,13 +119,11 @@ class Service
|
|
119
119
|
|
120
120
|
def complete(lines)
|
121
121
|
linepos = lines.shift.to_i
|
122
|
-
context =
|
123
|
-
@logger.debug("context element: #{@lang.identifier_provider.call(context, nil)}") if @logger
|
122
|
+
context = ContextBuilder.build_context(@lang, lines, linepos)
|
123
|
+
@logger.debug("context element: #{@lang.identifier_provider.call(context.element, nil)}") if @logger
|
124
124
|
current_line = lines.pop
|
125
125
|
current_line ||= ""
|
126
|
-
options = @completer.complete(
|
127
|
-
proc {|i| lines[-i]},
|
128
|
-
proc {|ref|
|
126
|
+
options = @completer.complete(context, lambda {|ref|
|
129
127
|
@service_provider.get_reference_completion_options(ref, context).collect {|o|
|
130
128
|
Completer::CompletionOption.new(o.identifier, "<#{o.type}>")}
|
131
129
|
})
|
@@ -148,10 +146,10 @@ class Service
|
|
148
146
|
|
149
147
|
def get_reference_targets(lines)
|
150
148
|
linepos = lines.shift.to_i
|
151
|
-
context = ContextElementBuilder.build_context_element(@lang, lines, linepos)
|
152
149
|
current_line = lines.last
|
150
|
+
context = ContextBuilder.build_context(@lang, lines, lines.last.size)
|
153
151
|
result = []
|
154
|
-
if current_line[linepos..linepos] =~ /[\w\/]/
|
152
|
+
if context && current_line[linepos..linepos] =~ /[\w\/]/
|
155
153
|
ident_start = (current_line.rindex(/[^\w\/]/, linepos) || -1)+1
|
156
154
|
ident_end = (current_line.index(/[^\w\/]/, linepos) || current_line.size)-1
|
157
155
|
ident = current_line[ident_start..ident_end]
|
@@ -161,7 +159,7 @@ class Service
|
|
161
159
|
result << "#{t.file};#{t.line};#{t.display_name}\n"
|
162
160
|
end
|
163
161
|
else
|
164
|
-
@service_provider.get_reference_targets(ident, context
|
162
|
+
@service_provider.get_reference_targets(ident, context).each do |t|
|
165
163
|
result << "#{t.file};#{t.line};#{t.display_name}\n"
|
166
164
|
end
|
167
165
|
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),"..","lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rgen/metamodel_builder'
|
5
|
+
require 'rtext/language'
|
6
|
+
require 'rtext/context_builder'
|
7
|
+
require 'rtext/completer'
|
8
|
+
|
9
|
+
class CompleterTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
module TestMM
|
12
|
+
extend RGen::MetamodelBuilder::ModuleExtension
|
13
|
+
class TestNode2 < RGen::MetamodelBuilder::MMBase
|
14
|
+
has_attr 'text', String
|
15
|
+
end
|
16
|
+
class TestNode < RGen::MetamodelBuilder::MMBase
|
17
|
+
has_attr 'text', String
|
18
|
+
has_attr 'unlabled1', String
|
19
|
+
has_attr 'unlabled2', Integer
|
20
|
+
has_many_attr 'nums', Integer
|
21
|
+
has_one 'related', TestNode
|
22
|
+
has_many 'others', TestNode
|
23
|
+
contains_many 'childs', TestNode, 'parent'
|
24
|
+
contains_one 'child2RoleA', TestNode2, 'parentA'
|
25
|
+
contains_many 'child2RoleB', TestNode2, 'parentB'
|
26
|
+
end
|
27
|
+
SomeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new(
|
28
|
+
:name => "SomeEnum", :literals => [:A, :B, :'non-word*chars'])
|
29
|
+
class TestNode3 < RGen::MetamodelBuilder::MMBase
|
30
|
+
has_attr 'bool', Boolean
|
31
|
+
has_attr 'float', Float
|
32
|
+
has_attr 'enum', SomeEnum
|
33
|
+
end
|
34
|
+
class TextNode < RGen::MetamodelBuilder::MMBase
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_after_command
|
39
|
+
options = complete TestMM, <<-END
|
40
|
+
TestNode |
|
41
|
+
END
|
42
|
+
assert_options([
|
43
|
+
["<unlabled1>", "<EString>"],
|
44
|
+
["nums:", "<EInt>"],
|
45
|
+
["others:", "<TestNode>"],
|
46
|
+
["related:", "<TestNode>"],
|
47
|
+
["text:", "<EString>"]
|
48
|
+
], options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_lable_prefix
|
52
|
+
options = complete TestMM, <<-END
|
53
|
+
TestNode t|
|
54
|
+
END
|
55
|
+
assert_options([
|
56
|
+
["text:", "<EString>"]
|
57
|
+
], options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_unlabled_prefix
|
61
|
+
options = complete TestMM, <<-END
|
62
|
+
TestNode u|
|
63
|
+
END
|
64
|
+
assert_options([
|
65
|
+
["<unlabled1>", "<EString>"]
|
66
|
+
], options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_after_labled_value
|
70
|
+
options = complete TestMM, <<-END
|
71
|
+
TestNode nums: 1, |
|
72
|
+
END
|
73
|
+
assert_options([
|
74
|
+
["others:", "<TestNode>"],
|
75
|
+
["related:", "<TestNode>"],
|
76
|
+
["text:", "<EString>"]
|
77
|
+
], options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_after_unlabled_value
|
81
|
+
options = complete TestMM, <<-END
|
82
|
+
TestNode "bla", |
|
83
|
+
END
|
84
|
+
assert_options([
|
85
|
+
["<unlabled2>", "<EInt>"],
|
86
|
+
["nums:", "<EInt>"],
|
87
|
+
["others:", "<TestNode>"],
|
88
|
+
["related:", "<TestNode>"],
|
89
|
+
["text:", "<EString>"]
|
90
|
+
], options)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_after_unlabled_value2
|
94
|
+
options = complete TestMM, <<-END
|
95
|
+
TestNode "bla", 1, |
|
96
|
+
END
|
97
|
+
assert_options([
|
98
|
+
["nums:", "<EInt>"],
|
99
|
+
["others:", "<TestNode>"],
|
100
|
+
["related:", "<TestNode>"],
|
101
|
+
["text:", "<EString>"]
|
102
|
+
], options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_after_array
|
106
|
+
options = complete TestMM, <<-END
|
107
|
+
TestNode nums: [1, 2], |
|
108
|
+
END
|
109
|
+
assert_options([
|
110
|
+
["others:", "<TestNode>"],
|
111
|
+
["related:", "<TestNode>"],
|
112
|
+
["text:", "<EString>"]
|
113
|
+
], options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_after_array_direct
|
117
|
+
options = complete TestMM, <<-END
|
118
|
+
TestNode nums: [1, 2]|
|
119
|
+
END
|
120
|
+
assert_options([
|
121
|
+
], options)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_value_int
|
125
|
+
options = complete TestMM, <<-END
|
126
|
+
TestNode nums: |
|
127
|
+
END
|
128
|
+
assert_options([
|
129
|
+
["0", nil],
|
130
|
+
["1", nil],
|
131
|
+
["2", nil],
|
132
|
+
["3", nil],
|
133
|
+
["4", nil]
|
134
|
+
], options)
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_value_boolean
|
138
|
+
options = complete TestMM, <<-END
|
139
|
+
TestNode3 bool: |
|
140
|
+
END
|
141
|
+
assert_options([
|
142
|
+
["true", nil],
|
143
|
+
["false", nil],
|
144
|
+
], options)
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_value_float
|
148
|
+
options = complete TestMM, <<-END
|
149
|
+
TestNode3 float: |
|
150
|
+
END
|
151
|
+
assert_options([
|
152
|
+
["0.0", nil],
|
153
|
+
["1.0", nil],
|
154
|
+
["2.0", nil],
|
155
|
+
["3.0", nil],
|
156
|
+
["4.0", nil]
|
157
|
+
], options)
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_value_enum
|
161
|
+
options = complete TestMM, <<-END
|
162
|
+
TestNode3 enum: |
|
163
|
+
END
|
164
|
+
assert_options([
|
165
|
+
["A", nil],
|
166
|
+
["B", nil],
|
167
|
+
["non-word*chars", nil]
|
168
|
+
], options)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_array_value
|
172
|
+
options = complete TestMM, <<-END
|
173
|
+
TestNode nums: [|
|
174
|
+
END
|
175
|
+
assert_options([
|
176
|
+
["0", nil],
|
177
|
+
["1", nil],
|
178
|
+
["2", nil],
|
179
|
+
["3", nil],
|
180
|
+
["4", nil]
|
181
|
+
], options)
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_array_value2
|
185
|
+
options = complete TestMM, <<-END
|
186
|
+
TestNode nums: [1,|
|
187
|
+
END
|
188
|
+
assert_options([
|
189
|
+
["0", nil],
|
190
|
+
["1", nil],
|
191
|
+
["2", nil],
|
192
|
+
["3", nil],
|
193
|
+
["4", nil]
|
194
|
+
], options)
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_reference_value
|
198
|
+
options = complete(TestMM, %Q(\
|
199
|
+
TestNode related: |\
|
200
|
+
), lambda { |r| [
|
201
|
+
RText::Completer::CompletionOption.new("A", "a"),
|
202
|
+
RText::Completer::CompletionOption.new("B", "b") ] })
|
203
|
+
assert_options([
|
204
|
+
["A", "a"],
|
205
|
+
["B", "b"],
|
206
|
+
], options)
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_reference_value_no_ref_completion_provider
|
210
|
+
options = complete TestMM, <<-END
|
211
|
+
TestNode related: |
|
212
|
+
END
|
213
|
+
assert_options([
|
214
|
+
], options)
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_children
|
218
|
+
options = complete TestMM, <<-END
|
219
|
+
TestNode {
|
220
|
+
|
|
221
|
+
END
|
222
|
+
assert_options([
|
223
|
+
["TestNode", "<unlabled1>, <unlabled2>"],
|
224
|
+
["child2RoleA:", "<TestNode2>"],
|
225
|
+
["child2RoleB:", "<TestNode2>"]
|
226
|
+
], options)
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_children_with_role
|
230
|
+
options = complete TestMM, <<-END
|
231
|
+
TestNode {
|
232
|
+
child2RoleA:
|
233
|
+
|
|
234
|
+
END
|
235
|
+
assert_options([
|
236
|
+
["TestNode2", ""],
|
237
|
+
], options)
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_children_with_role_array
|
241
|
+
options = complete TestMM, <<-END
|
242
|
+
TestNode {
|
243
|
+
child2RoleB: [
|
244
|
+
|
|
245
|
+
END
|
246
|
+
assert_options([
|
247
|
+
["TestNode2", ""],
|
248
|
+
], options)
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_children_prefix
|
252
|
+
options = complete TestMM, <<-END
|
253
|
+
TestNode {
|
254
|
+
child2RoleB: [
|
255
|
+
X|
|
256
|
+
END
|
257
|
+
assert_options([
|
258
|
+
], options)
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_children_inside_childrole
|
262
|
+
options = complete TestMM, <<-END
|
263
|
+
TestNode {
|
264
|
+
child2RoleA:
|
265
|
+
TestNode2 |
|
266
|
+
END
|
267
|
+
assert_options([
|
268
|
+
["text:", "<EString>"]
|
269
|
+
], options)
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_children_inside_childrole_array
|
273
|
+
options = complete TestMM, <<-END
|
274
|
+
TestNode {
|
275
|
+
child2RoleB: [
|
276
|
+
TestNode2 |
|
277
|
+
END
|
278
|
+
assert_options([
|
279
|
+
["text:", "<EString>"]
|
280
|
+
], options)
|
281
|
+
end
|
282
|
+
|
283
|
+
def test_root
|
284
|
+
options = complete TestMM, <<-END
|
285
|
+
|
|
286
|
+
END
|
287
|
+
assert_options([
|
288
|
+
["TestNode", "<unlabled1>, <unlabled2>"],
|
289
|
+
["TestNode2", ""],
|
290
|
+
["TestNode3", ""],
|
291
|
+
["TextNode", ""]
|
292
|
+
], options)
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_root_no_context_lines
|
296
|
+
options = complete TestMM, ""
|
297
|
+
assert_options([
|
298
|
+
["TestNode", "<unlabled1>, <unlabled2>"],
|
299
|
+
["TestNode2", ""],
|
300
|
+
["TestNode3", ""],
|
301
|
+
["TextNode", ""]
|
302
|
+
], options)
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_root_prefix
|
306
|
+
options = complete TestMM, <<-END
|
307
|
+
Text|
|
308
|
+
END
|
309
|
+
assert_options([
|
310
|
+
["TextNode", ""]
|
311
|
+
], options)
|
312
|
+
end
|
313
|
+
|
314
|
+
def assert_options(expected, options)
|
315
|
+
assert_equal(expected, options.collect { |o| [o.text, o.extra] })
|
316
|
+
end
|
317
|
+
|
318
|
+
def complete(mm, text, ref_comp_option_provider=nil)
|
319
|
+
context_lines = text.split("\n")
|
320
|
+
if context_lines.last
|
321
|
+
pos_in_line = context_lines.last.index("|")
|
322
|
+
context_lines.last.sub!("|", "")
|
323
|
+
end
|
324
|
+
lang = RText::Language.new(mm.ecore,
|
325
|
+
:root_classes => mm.ecore.eAllClasses,
|
326
|
+
:unlabled_arguments => lambda {|c| ["unlabled1", "unlabled2"]})
|
327
|
+
context = RText::ContextBuilder.build_context(lang, context_lines, pos_in_line)
|
328
|
+
RText::Completer.new(lang).complete(context, ref_comp_option_provider)
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|