rtext 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, lines, linepos)
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
- current_line = lines.last
44
- linestart = current_line[0..linepos-1]
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
- result << OpenElementChoice.new(display_name, path, @lang.line_number(e))
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
@@ -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) do |*args|
51
- if args[0]
52
- create_element(*args)
53
- else
54
- unassociated_comments(args[3])
55
- end
56
- end
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 = @lang.class_by_command(command.value)
79
- if !clazz
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
@@ -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 an
91
- # element, the comment as a string, and the environment to which the element has been
92
- # added to. then environment may be nil. it should add the comment to the element and
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
- @class_by_command = {}
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
- ((!options.has_key?(:short_class_names) || options[:short_class_names]) ?
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[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, &visitor)
10
- @visitor = visitor
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
- @visitor.call(command, arg_list, element_list, comments, is_root)
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
- @visitor.call(nil, nil, nil, comments, nil)
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)
@@ -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
- if @lang.containments_by_target_type(f.eContainingClass, f.eType).size > 1
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 =~ /^\d|\W/
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/context_element_builder'
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 = ContextElementBuilder.build_context_element(@lang, lines, linepos)
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(current_line, linepos,
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, lines, linepos).each do |t|
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
+