rtext 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9cc23fae2c92b80a90a57f2d0271539f47e3838f
4
- data.tar.gz: 8f992d322e1a2ed3c2725916aa668e15673f9928
3
+ metadata.gz: e9e6e9cf51849833ea57ae6f44b6e1b56bc715af
4
+ data.tar.gz: c63bf26429dee687a2ae1e07e63626a461a493f9
5
5
  SHA512:
6
- metadata.gz: 9d07b5485e5929f3d966d048c11fe2eec7630076e06c88de7fd99182a48f2bd1e0bcdf50ba68591c68968e44a401ff580a61b4b7440c34bb992f16275bd563a8
7
- data.tar.gz: 8ac1a2998803fffbf53b28d80ad138017629a6b7d23bf490554959a1210e571e180a75d01d08a229e472d09a9e5b011c9c69f629e1f61c704ca578b7689e116b
6
+ metadata.gz: ebe766d35ad7ccb86a7ada1cd5344cf4463fb3318ff2cf40613294e462542b63b464e150f747abe33af2193c94fd747cf6a776c64dcdb0f7535aff55cc37e01d
7
+ data.tar.gz: 7d576b493c98b55ff22a50e0bda0ed928adb765b032f7bf83fc10edc52e3e26e6067fce2aa6e6ae03b9d24358d7c7c964388030439599c057902130491721f5a
data/CHANGELOG CHANGED
@@ -87,3 +87,12 @@
87
87
  * Fixed frontend content extraction to support line breaks with backslash
88
88
  * Fixed frontend content extraction, joining of broken lines
89
89
 
90
+ =0.8.2
91
+
92
+ * Fixed serializer negative decimal quotation
93
+ * Added Object attribute test case
94
+
95
+ =0.9.0
96
+
97
+ * Added protocol versioning support
98
+ * Added completion options for square brackets and curly braces
@@ -4,6 +4,10 @@ RText frontend and backend pass messages containing JSON objects.
4
4
  Normally the frontend invokes a backend command by means of a request message and the
5
5
  backend will eventually reply with a response message.
6
6
 
7
+ == Versioning
8
+
9
+ RText protocol supports versioning mechanism with a single version natural number.
10
+ The actual version of the protocol is 1.
7
11
 
8
12
  == Encoding
9
13
 
@@ -25,8 +29,8 @@ In addition, the character "%" is escaped in the same way, i.e. "%" will always
25
29
 
26
30
  Example:
27
31
 
28
- The word "�bung" (german: exercise), encoded in ISO-8859-1 would result in the string:
29
- "%dcbung" (the "" Umlaut has a byte value of 0xdc is ISO-8859-1).
32
+ The word "Übung" (german: exercise), encoded in ISO-8859-1 would result in the string:
33
+ "%dcbung" (the "Ü" Umlaut has a byte value of 0xdc is ISO-8859-1).
30
34
 
31
35
 
32
36
  == Request Messages
@@ -34,9 +38,12 @@ The word "
34
38
  Each request message contains a field ``command`` and a field ``invocation_id``.
35
39
  The command field holds the name of the command to be invoked as a string.
36
40
  The invocation id field holds an identifier which will be repeated by the backend's response.
41
+ The "version" field was introduced in version 1. It is optional for protocol version 0 requests,
42
+ but mandatory for newer versions.
37
43
 
38
44
  {
39
45
  "type": "request",
46
+ "version": <protocol_version [integer]>,
40
47
  "command": <command>,
41
48
  "invocation_id": <invocation_id>
42
49
  ...
@@ -65,6 +72,16 @@ There are a number of error message which may be sent in response to a request m
65
72
  "invocation_id": <invocation_id>,
66
73
  "command": <unknown command [string]>
67
74
  }
75
+
76
+ === Unsupported Protocol Version Error
77
+
78
+ Introduced in version 1.
79
+
80
+ {
81
+ "type": "unsupported_version",
82
+ "invocation_id": <invocation_id>,
83
+ "version": <supported version [integer]>
84
+ }
68
85
 
69
86
  == Progress Information
70
87
 
@@ -90,6 +107,25 @@ The message field may carry information about the currently ongoing subtask.
90
107
  For each command, the layout of the request and response messages will be given below.
91
108
  Note that the invocation id field is present in every request and response but is omitted for brevity.
92
109
 
110
+ === Backend version request
111
+
112
+ Introduced in version 1.
113
+
114
+ This command requests the backend to disclose its supported protocol version.
115
+
116
+ {
117
+ "type": "request",
118
+ "command": "version"
119
+ }
120
+
121
+ The backend responds with a version information.
122
+
123
+ {
124
+ "type": "response",
125
+ "version": <version [integer]>
126
+ }
127
+
128
+ The backend supporting protocool version 0 only responds with an 'Unknown Command Error'.
93
129
 
94
130
  === Load Model
95
131
 
@@ -141,7 +177,14 @@ build the set of context lines in the frontend. Column number start at 1.
141
177
  }
142
178
 
143
179
  The backend replies with a list of completion options.
144
- The field ``insert`` holds the text to be inserted if the completion option is chosen.
180
+ The field ``insert`` holds the text to be inserted if the completion option is chosen. This text
181
+ may contains placeholders for cursor position. An editor may use them to assist a user in
182
+ filling additional completion fields.
183
+ Each placeholder must start and end with a vertical bar character (``|``) and can contain up to
184
+ three optional parts separated by an additional vertical bar character: ordering number, name and
185
+ description of this cursor position. E.g.: ``||``, ``|1|name|Entity name|``, ``|||New value|``.
186
+ Placeholders with the same name must be considered as the same value repeated in different
187
+ positions.
145
188
  The field ``display`` contains the string which should be displayed to the user in some kind of
146
189
  completion option menu. An optional description field may provide more information about a
147
190
  particular option.
data/Rakefile CHANGED
@@ -8,14 +8,14 @@ DocFiles = [
8
8
 
9
9
  RTextGemSpec = Gem::Specification.new do |s|
10
10
  s.name = %q{rtext}
11
- s.version = "0.8.2"
11
+ s.version = "0.9.0"
12
12
  s.date = Time.now.strftime("%Y-%m-%d")
13
13
  s.summary = %q{Ruby Textual Modelling}
14
14
  s.email = %q{martin dot thiede at gmx de}
15
15
  s.homepage = %q{http://ruby-gen.org}
16
16
  s.description = %q{RText can be used to derive textual languages from an RGen metamodel with very little effort.}
17
17
  s.authors = ["Martin Thiede"]
18
- s.add_dependency('rgen', '>= 0.6.1')
18
+ s.add_dependency('rgen', '>= 0.8.2')
19
19
  gemfiles = Rake::FileList.new
20
20
  gemfiles.include("{lib,test}/**/*")
21
21
  gemfiles.include(DocFiles)
@@ -26,7 +26,8 @@ module RText
26
26
  #
27
27
  module ContextBuilder
28
28
 
29
- Context = Struct.new(:element, :feature, :prefix, :in_array, :in_block, :after_label, :problem)
29
+ PositionContext = Struct.new(:in_array, :in_block, :after_label, :after_comma, :before_brace, :before_bracket)
30
+ Context = Struct.new(:element, :feature, :prefix, :problem, :position, :line_indent, :indent)
30
31
 
31
32
  class << self
32
33
  include RText::Tokenizer
@@ -50,13 +51,37 @@ module ContextBuilder
50
51
  else
51
52
  feature = nil
52
53
  end
53
- Context.new(element, feature, context_info.prefix, context_info.in_array, context_info.in_block, after_label, context_info.problem)
54
+ context_info.position.after_label = after_label
55
+ Context.new(element, feature, context_info.prefix, context_info.problem, context_info.position,
56
+ get_line_indent(context_lines.last), get_indent(context_lines))
54
57
  else
55
- Context.new(nil, nil, context_info.prefix, context_info.in_array, context_info.in_block, false, context_info.problem)
58
+ context_info.position.after_label = false
59
+ Context.new(nil, nil, context_info.prefix, context_info.problem, context_info.position,
60
+ get_line_indent(context_lines.last), get_indent(context_lines))
56
61
  end
57
62
  end
58
63
 
59
64
  private
65
+
66
+ def get_line_indent(line)
67
+ return '' unless line
68
+ match = line.match(/^\s+/)
69
+ if match.nil?
70
+ ''
71
+ else
72
+ match[0]
73
+ end
74
+ end
75
+
76
+ # Compute indent from context lines
77
+ def get_indent(context_lines)
78
+ cl = context_lines.dup
79
+ while true
80
+ return ' ' if cl.size < 2 # default indentation is 2 spaces
81
+ indent = get_line_indent(cl.last)[get_line_indent(cl.delete_at(-2)).size..-1]
82
+ return indent unless indent.nil? || indent.empty?
83
+ end
84
+ end
60
85
 
61
86
  def instantiate_context_element(language, context_info)
62
87
  root_elements = []
@@ -72,7 +97,7 @@ module ContextBuilder
72
97
  end
73
98
 
74
99
  def find_leaf_child(element, num_required_children)
75
- childs = element.class.ecore.eAllReferences.select{|r| r.containment}.collect{|r|
100
+ childs = element.class.ecore.eAllReferences.select{|r| r.containment }.collect{|r|
76
101
  element.getGenericAsArray(r.name)}.flatten
77
102
  if num_required_children == 0
78
103
  element
@@ -83,7 +108,7 @@ module ContextBuilder
83
108
  end
84
109
  end
85
110
 
86
- ContextInternal = Struct.new(:lines, :num_elements, :role, :prefix, :in_array, :in_block, :problem)
111
+ ContextInternal = Struct.new(:lines, :num_elements, :role, :prefix, :problem, :position)
87
112
 
88
113
  # extend +context_lines+ into a set of lines which can be processed by the RText
89
114
  def fix_context(language, context_lines, position_in_line)
@@ -95,11 +120,14 @@ module ContextBuilder
95
120
  position_in_line ||= context_lines.last.size
96
121
  # cut off last line right of cursor
97
122
  if position_in_line < 1
98
- context_lines.pop
123
+ tail = context_lines.pop
99
124
  context_lines << ""
100
125
  else
126
+ tail = context_lines.last[position_in_line..-1]
101
127
  context_lines << context_lines.pop[0..position_in_line-1]
102
128
  end
129
+ before_brace = !tail.nil? && !tail.match(/^\s*\{/).nil?
130
+ before_bracket = !tail.nil? && !tail.match(/^\s*\[/).nil?
103
131
  problem = nil
104
132
  line = context_lines.last
105
133
  if line =~ /\{\s*$/
@@ -110,7 +138,7 @@ module ContextBuilder
110
138
  problem = :after_curly
111
139
  end
112
140
 
113
- num_elements = in_block = in_array = missing_comma = role = prefix = nil
141
+ num_elements = in_block = in_array = missing_comma = role = prefix = after_comma = nil
114
142
  tokens = tokenize(line, language.reference_regexp)
115
143
  tokens.pop if tokens.last && tokens.last.kind == :newline
116
144
  if tokens.size > 0 && tokens[0].kind == :identifier
@@ -124,6 +152,7 @@ module ContextBuilder
124
152
  unlabled_index = 0
125
153
  tokens[1..-1].each do |token|
126
154
  break if token.kind == :error
155
+ after_comma = false
127
156
  if token.kind == "["
128
157
  in_array = true
129
158
  elsif token.kind == "]"
@@ -136,6 +165,7 @@ module ContextBuilder
136
165
  missing_comma = false
137
166
  role = nil unless in_array
138
167
  unlabled_index += 1 unless in_array
168
+ after_comma = true
139
169
  end
140
170
  end
141
171
  if ((tokens.size == 1 && line =~ /\s+$/) ||
@@ -187,6 +217,16 @@ module ContextBuilder
187
217
  if context_lines[-2] =~ /^\s*\w+:\s*$/
188
218
  context_lines[-1] = context_lines.pop
189
219
  end
220
+ elsif tokens.size == 1 && tokens[0].kind == :label
221
+ token = tokens[0]
222
+ role = context_lines.last[token.scol - 1..token.ecol - 2]
223
+ context_lines << context_lines.pop[0..token.scol - 2]
224
+ context = fix_context(language, context_lines, context_lines.last.size)
225
+ context.role = role
226
+ context.position.in_block = false
227
+ context.position.before_brace = before_brace
228
+ context.position.before_bracket = before_bracket
229
+ return context
190
230
  else
191
231
  # comment, closing brackets, etc.
192
232
  num_elements = 0
@@ -210,7 +250,8 @@ module ContextBuilder
210
250
  end
211
251
  end
212
252
  problem = :missing_comma if !problem && missing_comma
213
- ContextInternal.new(context_lines, num_elements, role, prefix, in_array, in_block, problem)
253
+ ContextInternal.new(context_lines, num_elements, role, prefix, problem,
254
+ PositionContext.new(in_array, in_block, false, after_comma, before_brace, before_bracket))
214
255
  end
215
256
 
216
257
  def find_role(context_lines)
@@ -4,7 +4,33 @@ module RText
4
4
 
5
5
  class DefaultCompleter
6
6
 
7
- CompletionOption = Struct.new(:text, :extra)
7
+ class CompletionOption
8
+
9
+ attr_accessor :insert
10
+ attr_accessor :display
11
+ attr_accessor :extra
12
+ attr_accessor :description
13
+
14
+ def self.from_text_extra(text, extra)
15
+ self.new(text, text + ' ' + extra, nil, extra)
16
+ end
17
+
18
+ def self.for_curly_braces(context)
19
+ self.new("{\n#{context.line_indent}#{context.indent}||\n#{context.line_indent}}", '{}')
20
+ end
21
+
22
+ def self.for_square_brackets
23
+ self.new('[ || ]', '[]', '')
24
+ end
25
+
26
+ def initialize(insert, display, description=nil, extra=nil)
27
+ @insert = insert
28
+ @display = display
29
+ @description = description
30
+ @extra = extra
31
+ end
32
+
33
+ end
8
34
 
9
35
  # Creates a completer for RText::Language +language+.
10
36
  #
@@ -14,23 +40,24 @@ class DefaultCompleter
14
40
 
15
41
  # Provides completion options
16
42
  #
17
- def complete(context)
43
+ def complete(context, version=0)
18
44
  clazz = context && context.element && context.element.class.ecore
19
45
  if clazz
20
- if context.in_block
46
+ if context.position.in_block
21
47
  block_options(context, clazz)
22
48
  elsif !context.problem
23
49
  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
50
+ add_value_options(context, result, version) if context.feature
51
+ add_label_options(context, clazz, result, version) unless context.position.after_label
30
52
  result
31
53
  else
32
54
  # missing comma, after curly brace, etc.
33
- []
55
+ if version > 0 && !context.position.before_brace &&
56
+ context.element.class.ecore.eAllReferences.any? { |r| r.containment }
57
+ [CompletionOption.for_curly_braces(context)]
58
+ else
59
+ []
60
+ end
34
61
  end
35
62
  elsif context
36
63
  root_options
@@ -52,7 +79,7 @@ class DefaultCompleter
52
79
  # all target types which don't need a label
53
80
  # and all lables which are needed by a potential target type
54
81
  @lang.containments(clazz).each do |r|
55
- ([r.eType] + r.eType.eAllSubTypes).select{|t| !t.abstract}.each do |t|
82
+ ([r.eType] + r.eType.eAllSubTypes).select{|t| t.concrete}.each do |t|
56
83
  if @lang.labeled_containment?(clazz, r) || @lang.containments_by_target_type(clazz, t).size > 1
57
84
  labled_refs << r
58
85
  else
@@ -66,14 +93,17 @@ class DefaultCompleter
66
93
  class_completion_option(c)
67
94
  end +
68
95
  labled_refs.uniq.collect do |r|
69
- CompletionOption.new("#{r.name}:", "<#{r.eType.name}>")
96
+ CompletionOption.from_text_extra("#{r.name}:", "<#{r.eType.name}>")
70
97
  end
71
98
  end
72
99
 
73
- def add_value_options(context, result)
100
+ def add_value_options(context, result, version)
74
101
  if context.feature.is_a?(RGen::ECore::EAttribute) || !context.feature.containment
75
102
  if context.feature.is_a?(RGen::ECore::EReference)
76
103
  result.concat(reference_options(context))
104
+ if version > 0 && !context.position.before_bracket && context.feature.upperBound != 1
105
+ result << CompletionOption.for_square_brackets
106
+ end
77
107
  elsif context.feature.eType.is_a?(RGen::ECore::EEnum)
78
108
  result.concat(enum_options(context))
79
109
  elsif context.feature.eType.instanceClass == String
@@ -88,16 +118,22 @@ class DefaultCompleter
88
118
  # no options
89
119
  end
90
120
  else
91
- # containment reference, ignore
121
+ if version > 0 && !context.position.before_bracket && context.feature.upperBound != 1
122
+ result << CompletionOption.for_square_brackets
123
+ end
92
124
  end
93
125
  end
94
126
 
95
- def add_label_options(context, clazz, result)
127
+ def add_label_options(context, clazz, result, version)
96
128
  result.concat(@lang.labled_arguments(clazz).
97
129
  select{|f|
98
130
  !context.element.eIsSet(f.name)}.collect do |f|
99
- CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
100
- end )
131
+ CompletionOption.from_text_extra("#{f.name}:", "<#{f.eType.name}>")
132
+ end )
133
+ if version > 0 && !context.position.after_comma &&
134
+ context.element.class.ecore.eAllReferences.any? { |r| r.containment } && !context.position.before_brace
135
+ result << CompletionOption.for_curly_braces(context)
136
+ end
101
137
  end
102
138
 
103
139
  def root_options
@@ -118,34 +154,43 @@ class DefaultCompleter
118
154
  lname = "\"#{lname.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
119
155
  gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
120
156
  end
121
- CompletionOption.new("#{lname}", "<#{context.feature.eType.name}>")
157
+ CompletionOption.from_text_extra("#{lname}", "<#{context.feature.eType.name}>")
122
158
  end
123
159
  end
124
160
 
125
161
  def string_options(context)
126
162
  if @lang.unquoted?(context.feature)
127
- [ CompletionOption.new("#{context.feature.name.gsub(/\W/,"")}", value_description(context)) ]
163
+ [ CompletionOption.from_text_extra("#{context.feature.name.gsub(/\W/,"")}", value_description(context)) ]
128
164
  else
129
- [ CompletionOption.new("\"\"", value_description(context)) ]
165
+ [ CompletionOption.from_text_extra("\"\"", value_description(context)) ]
130
166
  end
131
167
  end
132
168
 
169
+ def get_default_value_completion(context)
170
+ return nil unless context.feature.defaultValue
171
+ CompletionOption.from_text_extra("#{context.feature.defaultValue}", value_description(context))
172
+ end
173
+
133
174
  def integer_options(context)
134
- (0..0).collect{|i| CompletionOption.new("#{i}", value_description(context)) }
175
+ default_comp = get_default_value_completion(context)
176
+ return [default_comp] if default_comp
177
+ (0..0).collect{|i| CompletionOption.from_text_extra("#{i}", value_description(context)) }
135
178
  end
136
179
 
137
180
  def float_options(context)
138
- (0..0).collect{|i| CompletionOption.new("#{i}.0", value_description(context)) }
181
+ default_comp = get_default_value_completion(context)
182
+ return [default_comp] if default_comp
183
+ (0..0).collect{|i| CompletionOption.from_text_extra("#{i}.0", value_description(context)) }
139
184
  end
140
185
 
141
186
  def boolean_options(context)
142
- [true, false].collect{|b| CompletionOption.new("#{b}", value_description(context)) }
187
+ [true, false].collect{|b| CompletionOption.from_text_extra("#{b}", value_description(context)) }
143
188
  end
144
189
 
145
190
  private
146
191
 
147
192
  def value_description(context)
148
- if context.after_label
193
+ if context.position.after_label
149
194
  "<#{context.feature.eType.name}>"
150
195
  else
151
196
  "[#{context.feature.name}] <#{context.feature.eType.name}>"
@@ -154,7 +199,7 @@ class DefaultCompleter
154
199
 
155
200
  def class_completion_option(eclass)
156
201
  uargs = @lang.unlabled_arguments(eclass).collect{|a| "<#{a.name}>"}.join(", ")
157
- CompletionOption.new(@lang.command_by_class(eclass.instanceClass), uargs)
202
+ CompletionOption.from_text_extra(@lang.command_by_class(eclass.instanceClass), uargs)
158
203
  end
159
204
 
160
205
  end
@@ -35,17 +35,17 @@ class DefaultServiceProvider
35
35
  end
36
36
  end
37
37
 
38
- def get_completion_options(context)
38
+ def get_completion_options(context, version=0)
39
39
  completer = RText::DefaultCompleter.new(@lang)
40
40
  class << completer
41
41
  attr_accessor :service_provider
42
42
  def reference_options(context)
43
43
  service_provider.get_reference_completion_options(context).collect {|o|
44
- DefaultCompleter::CompletionOption.new(o.identifier, "<#{o.type}>")}
44
+ DefaultCompleter::CompletionOption.from_text_extra(o.identifier, "<#{o.type}>")}
45
45
  end
46
46
  end
47
47
  completer.service_provider = self
48
- completer.complete(context)
48
+ completer.complete(context, version)
49
49
  end
50
50
 
51
51
  ReferenceCompletionOption = Struct.new(:identifier, :type)