rtext 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/MIT-LICENSE +20 -0
- data/README +29 -0
- data/RText_Plugin_Implementation_Guide +247 -0
- data/RText_Users_Guide +31 -0
- data/Rakefile +46 -0
- data/lib/rtext/completer.rb +152 -0
- data/lib/rtext/context_element_builder.rb +112 -0
- data/lib/rtext/default_loader.rb +137 -0
- data/lib/rtext/default_service_provider.rb +153 -0
- data/lib/rtext/instantiator.rb +284 -0
- data/lib/rtext/language.rb +257 -0
- data/lib/rtext/parser.rb +251 -0
- data/lib/rtext/serializer.rb +190 -0
- data/lib/rtext/service.rb +182 -0
- data/lib/rtext_plugin/connection_manager.rb +59 -0
- data/test/instantiator_test.rb +931 -0
- data/test/rtext_test.rb +5 -0
- data/test/serializer_test.rb +418 -0
- metadata +84 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Martin Thiede
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
== RText - Ruby Textual Modelling
|
2
|
+
|
3
|
+
RText can be used to derive textual languages from an RGen metamodel with very little effort.
|
4
|
+
|
5
|
+
RText features include:
|
6
|
+
* Generic textual syntax
|
7
|
+
* Generic instantiator (parser) and serializer
|
8
|
+
* Consistency checks derived from the metamodel
|
9
|
+
* Designed to work with lightweight editor plugins providing
|
10
|
+
- Syntax Highlighting
|
11
|
+
- Auto Completion on language commands and model references
|
12
|
+
- Error Annotations
|
13
|
+
- Model Navigation
|
14
|
+
|
15
|
+
|
16
|
+
= Installation
|
17
|
+
|
18
|
+
> gem install rtext
|
19
|
+
|
20
|
+
|
21
|
+
= Documentation
|
22
|
+
|
23
|
+
|
24
|
+
= Examples
|
25
|
+
|
26
|
+
|
27
|
+
= License
|
28
|
+
|
29
|
+
RGen is released under the MIT license.
|
@@ -0,0 +1,247 @@
|
|
1
|
+
=RText Plugin Implementation Guide
|
2
|
+
|
3
|
+
RText editors consist of a frontend and a backend. Most of the logic is implemented
|
4
|
+
within the backend while the frontend is kept as thin as possible. This should simplify
|
5
|
+
the task of writing a frontend and promote development of RText frontends for different
|
6
|
+
editing environments.
|
7
|
+
|
8
|
+
This guide explains how to build a RText frontend which will typically be a plugin for an
|
9
|
+
existing editor.
|
10
|
+
|
11
|
+
|
12
|
+
==Plugin Responsibilies
|
13
|
+
|
14
|
+
Here is a rough overview of what a RText plugin has to do:
|
15
|
+
|
16
|
+
* find the .rtext config file for a given RText model file
|
17
|
+
* start the corresponding backend process if not started already
|
18
|
+
* provide a way to reload the model and issue the refresh command to the backend
|
19
|
+
* implement syntax highlighting for the generic RText syntax
|
20
|
+
* forward auto completion requests to the backend, let the user choose from the returned options
|
21
|
+
and insert the option chosen
|
22
|
+
* forward element search requests to the backend, let the user choose from the matched elements
|
23
|
+
and jump to the element chosen
|
24
|
+
* forward reference target requests to the backend, let the user choose from the targets found
|
25
|
+
and jump to the target chosen
|
26
|
+
* query the backend for model problems and display them to the user
|
27
|
+
* stop the backend process when it's no longer needed
|
28
|
+
|
29
|
+
|
30
|
+
==Plugin Implementation Hints
|
31
|
+
|
32
|
+
The plugin implementation could include the following entities.
|
33
|
+
|
34
|
+
===Command
|
35
|
+
|
36
|
+
A command issued by the plugin and sent to the backend service. See the section about the
|
37
|
+
Frontend/Backend Communication Protocol below for details about available commands.
|
38
|
+
|
39
|
+
|
40
|
+
===Connector
|
41
|
+
|
42
|
+
A connector represents the connection to a running backend process.
|
43
|
+
Each connector/process is associated with a specific .rtext file and a specific service specification
|
44
|
+
within this file. See the RText Users Guide for details about the .rtext config file.
|
45
|
+
|
46
|
+
When the backend service is started, it outputs the UDP port it is listening on. The connector
|
47
|
+
should use this port to send commands to the backend.
|
48
|
+
|
49
|
+
The connector should send commands to the backend. It should assign invocation ids and keep the
|
50
|
+
invocation until a response is received or a timeout has occured.
|
51
|
+
|
52
|
+
When the backend responds, the connector should associate the response packages with the previous
|
53
|
+
request using the invocation id. It should also join fragmented response packages and send an
|
54
|
+
internal notification when a response has been fully received.
|
55
|
+
|
56
|
+
|
57
|
+
===Connector Manager
|
58
|
+
|
59
|
+
The connector manager should be able to return a connector for a given RText model file.
|
60
|
+
|
61
|
+
It needs to identify the backend process to which to connect by means of the .rtext config file and
|
62
|
+
a contained service specification (one .rtext files may contain service specifications for starting
|
63
|
+
different backend services depending on the model file name).
|
64
|
+
|
65
|
+
For this, it needs to find the first .rtext config file containing a service specification matching
|
66
|
+
the given RText model file name. See the RText Users guide for details about the .rtext config file
|
67
|
+
and the way how they are found in the filesystem.
|
68
|
+
|
69
|
+
Once the .rtext file/service specification is found it can be used as a key for a lookup for
|
70
|
+
already established connections. The plugin should avoid starting the same process twice. All
|
71
|
+
RText model files with the same file extension and belonging to the same .rtext file should
|
72
|
+
use the same process.
|
73
|
+
|
74
|
+
The process should be kept running as long as editing is in progress. This will greatly
|
75
|
+
reduce response times as the model can be kept in memory and only the changed parts need
|
76
|
+
to be reloaded.
|
77
|
+
|
78
|
+
When the plugin lifecycle ends, the connector manager should send the "stop" command to the
|
79
|
+
backend processes.
|
80
|
+
|
81
|
+
|
82
|
+
==Frontend/Backend Communication Protocol
|
83
|
+
|
84
|
+
Communication takes place via plain text transmitted via UDP. There are requests by
|
85
|
+
the frontend and responses by the backend. In both cases the text is interpreted as
|
86
|
+
a set of lines (separated by \n or \r\n).
|
87
|
+
|
88
|
+
|
89
|
+
===Request
|
90
|
+
|
91
|
+
The request consists of a command id, an invocation id and the command parameters.
|
92
|
+
The invocation id is repeated within the response to allow proper association with the
|
93
|
+
request. Command parameters are command specific (see below).
|
94
|
+
|
95
|
+
Requests can not span more than one UDP package and thus are limited to the UDP payload size.
|
96
|
+
|
97
|
+
line 1: <command id>
|
98
|
+
line 2: <invocation id>
|
99
|
+
line 3..n: <command parameters>
|
100
|
+
|
101
|
+
|
102
|
+
===Response
|
103
|
+
|
104
|
+
Each response contains the invocation id of the request in the first line.
|
105
|
+
Since it could be splitted over several UDP packages it contains an indication
|
106
|
+
in the second line if more packages are following ("more") or this is the last package
|
107
|
+
("last"). The rest of the response is a fragment of the command result data. All fragments
|
108
|
+
of a set of response packages with the same invocation id need to be joined.
|
109
|
+
|
110
|
+
Note that there are no mechanisms to protect agains package loss or reordering since
|
111
|
+
this protocol is intended to be used on local socket connections only.
|
112
|
+
|
113
|
+
line 1: <invocation id>
|
114
|
+
line 2: "more" | "last"
|
115
|
+
line 3..n: <command result data>
|
116
|
+
|
117
|
+
|
118
|
+
===Command: Refresh
|
119
|
+
|
120
|
+
The refresh command tells the backend to reload the model from the file system. Frontends
|
121
|
+
could issue this command after a model file has been changed in the file system or on
|
122
|
+
an explicit user request.
|
123
|
+
|
124
|
+
Request Format:
|
125
|
+
command id: "refresh"
|
126
|
+
no parameters
|
127
|
+
|
128
|
+
Response Format:
|
129
|
+
no result data
|
130
|
+
|
131
|
+
|
132
|
+
===Command: Complete
|
133
|
+
|
134
|
+
This command is a request by the frontend to show auto completion options at a given
|
135
|
+
location within a file. The location is expressed using a set of context lines and the cursor
|
136
|
+
column position in the current line.
|
137
|
+
|
138
|
+
Context lines are lines from an RText file which contain a (context) command and all
|
139
|
+
the parent commands wrapped around it. Any sibling commands can be omitted as well as
|
140
|
+
any lines containing closing braces and brackets. The order of lines is the same as in the RText file.
|
141
|
+
|
142
|
+
Here is an example. Consider the following RText file with the cursor in the line of "Command3" at
|
143
|
+
the time when the auto completion command is issued.
|
144
|
+
|
145
|
+
Command1 {
|
146
|
+
Command2 {
|
147
|
+
role1: [
|
148
|
+
Command3 <== cursor in this line
|
149
|
+
Command4
|
150
|
+
]
|
151
|
+
}
|
152
|
+
Command5
|
153
|
+
}
|
154
|
+
|
155
|
+
The context lines in this case would be the following.
|
156
|
+
|
157
|
+
Command1 {
|
158
|
+
Command2 {
|
159
|
+
role1: [
|
160
|
+
Command3
|
161
|
+
|
162
|
+
The current line is always the last of the context lines.
|
163
|
+
|
164
|
+
Note that all siblings of the command and parent commands have been stripped off, as well as
|
165
|
+
any closing braces or brackets.
|
166
|
+
|
167
|
+
The purpose of this special context line format is to keep the task of extracting the
|
168
|
+
context in the frontend simple and the amount of data transmitted to the backend low.
|
169
|
+
It's also a way to keep the parsing time of the context low in the backend and thus to minimize
|
170
|
+
the user noticable delay.
|
171
|
+
|
172
|
+
Request Format:
|
173
|
+
command id: "complete"
|
174
|
+
param line 1: cursor column position in the current line
|
175
|
+
param line 2..n: context lines
|
176
|
+
|
177
|
+
Response Format:
|
178
|
+
result line 1..n: <completion string>;<extra info string>
|
179
|
+
|
180
|
+
|
181
|
+
===Command: Show Problems
|
182
|
+
|
183
|
+
This command is a request by the frontend to determine and return all problems present in the current model.
|
184
|
+
The command implicitly reloads the model from the filesystem before checking for problems.
|
185
|
+
|
186
|
+
Request Format:
|
187
|
+
command id: "show_problems"
|
188
|
+
no parameters
|
189
|
+
|
190
|
+
Response Format:
|
191
|
+
result line n: <file name>
|
192
|
+
result line n+1..n+m <line number>;<message>
|
193
|
+
|
194
|
+
Note that the file name is repeated only once for all problems within a file to reduce the amout of
|
195
|
+
result data which needs to be transmitted.
|
196
|
+
|
197
|
+
|
198
|
+
===Command: Reference Targets
|
199
|
+
|
200
|
+
This command is used to retrieve the targets of a given reference. The reference location is expressed
|
201
|
+
by means of a set of context lines and the cursor column position in the current line. The cursor
|
202
|
+
column position must be within the string representing the reference. The format of the context lines
|
203
|
+
is the same as the one described with the "Complete" command (see above).
|
204
|
+
|
205
|
+
Note that the service provider is free to define how a reference is represented. In particular it
|
206
|
+
can interpret the command identifier as a reference and use this mechanism to show incoming references
|
207
|
+
(reverse references).
|
208
|
+
|
209
|
+
As references can be ambiguous, the result is a list of possible targets. Each target consists of the
|
210
|
+
filename and line number of the target element as well as the text to be displayed to the user in case
|
211
|
+
there are more than one targets to choose from.
|
212
|
+
|
213
|
+
Request Format:
|
214
|
+
command id: "get_reference_targets"
|
215
|
+
param line 1: cursor column position in the current line
|
216
|
+
param line 2..n: context lines
|
217
|
+
|
218
|
+
Response Format:
|
219
|
+
result line 1..n: <file name>;<line number>;<display name>
|
220
|
+
|
221
|
+
|
222
|
+
===Command: Find Elements
|
223
|
+
|
224
|
+
This command returns a set of model elements matching a search pattern. The format of the
|
225
|
+
pattern depends on the service provider. In a simple case, for example, the pattern could be the
|
226
|
+
beginning of the element names.
|
227
|
+
|
228
|
+
Request Format:
|
229
|
+
command id: "get_elements"
|
230
|
+
param line 1: <seach pattern>
|
231
|
+
|
232
|
+
Response Format:
|
233
|
+
result line 1..n: <display name>;<file name>;<line number>
|
234
|
+
|
235
|
+
|
236
|
+
===Command: Stop
|
237
|
+
|
238
|
+
This command is normally invoked when the frontend terminates or otherwise needs to terminate
|
239
|
+
the backend service. When receiving this command, the backend will terminate.
|
240
|
+
|
241
|
+
Request Format:
|
242
|
+
command id: "stop"
|
243
|
+
no parameters
|
244
|
+
|
245
|
+
Response Format:
|
246
|
+
no result data
|
247
|
+
|
data/RText_Users_Guide
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
=RText Users Guide
|
2
|
+
|
3
|
+
== The .rtext config file
|
4
|
+
|
5
|
+
A .rtext config file is used to specify which backend service is to be started for a given
|
6
|
+
RText model file.
|
7
|
+
|
8
|
+
In order to be found by an RText plugin, the .rtext file must be located in the same directory
|
9
|
+
as the RText model file being edited or in any of its parent directories.
|
10
|
+
|
11
|
+
Here is the grammar for the config file syntax:
|
12
|
+
|
13
|
+
rtext_config ::= <service_spec>+
|
14
|
+
service_spec ::= <file_pattern_list>:\n<command line string>\n
|
15
|
+
file_pattern_list ::= <file_pattern> | <file_pattern>, <file_pattern_list>
|
16
|
+
file_pattern ::= <filename without path> | *.<extension>
|
17
|
+
|
18
|
+
Here is an example:
|
19
|
+
|
20
|
+
*.ext1:
|
21
|
+
ruby service1.rb
|
22
|
+
*.ext2, *.ext3:
|
23
|
+
ruby service2.rb *.ext2 *.ext3
|
24
|
+
noext:
|
25
|
+
service3.sh
|
26
|
+
|
27
|
+
This example contains specifications for three different backend services. service1.rb is started
|
28
|
+
when a model file with the extension "ext1" is being edited. service2.rb is started when a
|
29
|
+
model file with extension "ext2" or "ext3" is edited. service2.rb also receives additional command
|
30
|
+
line parameters. service3.sh is started whenever a file named "noext" is edited.
|
31
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rake/gempackagetask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
DocFiles = [
|
5
|
+
"README", "CHANGELOG", "MIT-LICENSE",
|
6
|
+
"RText_Users_Guide",
|
7
|
+
"RText_Plugin_Implementation_Guide"]
|
8
|
+
|
9
|
+
RTextGemSpec = Gem::Specification.new do |s|
|
10
|
+
s.name = %q{rtext}
|
11
|
+
s.version = "0.2.0"
|
12
|
+
s.date = Time.now.strftime("%Y-%m-%d")
|
13
|
+
s.summary = %q{Ruby Textual Modelling}
|
14
|
+
s.email = %q{martin dot thiede at gmx de}
|
15
|
+
s.homepage = %q{http://ruby-gen.org}
|
16
|
+
s.description = %q{RText can be used to derive textual languages from an RGen metamodel with very little effort.}
|
17
|
+
s.authors = ["Martin Thiede"]
|
18
|
+
s.add_dependency('rgen', '>= 0.6.0')
|
19
|
+
gemfiles = Rake::FileList.new
|
20
|
+
gemfiles.include("{lib,test}/**/*")
|
21
|
+
gemfiles.include(DocFiles)
|
22
|
+
gemfiles.include("Rakefile")
|
23
|
+
gemfiles.exclude(/\b\.bak\b/)
|
24
|
+
s.files = gemfiles
|
25
|
+
s.rdoc_options = ["--main", "README", "-x", "test"]
|
26
|
+
s.extra_rdoc_files = DocFiles
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::RDocTask.new do |rd|
|
30
|
+
rd.main = "README"
|
31
|
+
rd.rdoc_files.include(DocFiles)
|
32
|
+
rd.rdoc_files.include("lib/**/*.rb")
|
33
|
+
rd.rdoc_dir = "doc"
|
34
|
+
end
|
35
|
+
|
36
|
+
RTextPackageTask = Rake::GemPackageTask.new(RTextGemSpec) do |p|
|
37
|
+
p.need_zip = false
|
38
|
+
end
|
39
|
+
|
40
|
+
task :prepare_package_rdoc => :rdoc do
|
41
|
+
RTextPackageTask.package_files.include("doc/**/*")
|
42
|
+
end
|
43
|
+
|
44
|
+
task :release => [:prepare_package_rdoc, :package]
|
45
|
+
|
46
|
+
task :clobber => [:clobber_rdoc, :clobber_package]
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'rgen/ecore/ecore_ext'
|
2
|
+
|
3
|
+
module RText
|
4
|
+
|
5
|
+
class Completer
|
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
|
+
# :linestart
|
18
|
+
# the content of the current line before the cursor
|
19
|
+
#
|
20
|
+
# :prev_line_provider
|
21
|
+
# is a proc which must return lines above the current line
|
22
|
+
# it receives an index parameter in the range 1..n
|
23
|
+
# 1 is the line just above the current one, 2 is the second line above, etc.
|
24
|
+
# the proc must return the line as a string or nil if there is no more line
|
25
|
+
#
|
26
|
+
# :ref_completion_option_provider
|
27
|
+
# a proc which receives a EReference and should return
|
28
|
+
# the possible completion options as CompletionOption objects
|
29
|
+
# note, that the context element may be nil if this information is unavailable
|
30
|
+
#
|
31
|
+
def complete(line, linepos, prev_line_provider, ref_completion_option_provider=nil)
|
32
|
+
linestart = line[0..linepos-1]
|
33
|
+
# command
|
34
|
+
if linestart =~ /^\s*(\w*)$/
|
35
|
+
prefix = $1
|
36
|
+
classes = completion_classes(prev_line_provider)
|
37
|
+
classes = classes.select{|c| c.name.index(prefix) == 0} if prefix
|
38
|
+
classes.sort{|a,b| a.name <=> b.name}.collect do |c|
|
39
|
+
uargs = @lang.unlabled_arguments(c).collect{|a| "<#{a.name}>"}.join(", ")
|
40
|
+
CompletionOption.new(c.name, uargs)
|
41
|
+
end
|
42
|
+
# attribute
|
43
|
+
elsif linestart =~ /^\s*(\w+)\s+(?:[^,]+,)*\s*(\w*)$/
|
44
|
+
command, prefix = $1, $2
|
45
|
+
clazz = @lang.class_by_command(command)
|
46
|
+
if clazz
|
47
|
+
features = @lang.labled_arguments(clazz.ecore)
|
48
|
+
features = features.select{|f| f.name.index(prefix) == 0} if prefix
|
49
|
+
features.sort{|a,b| a.name <=> b.name}.collect do |f|
|
50
|
+
CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
|
51
|
+
end
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
# value
|
56
|
+
elsif linestart =~ /\s*(\w+)\s+(?:[^,]+,)*\s*(\w+):\s*(\S*)$/
|
57
|
+
command, fn, prefix = $1, $2, $3
|
58
|
+
clazz = @lang.class_by_command(command)
|
59
|
+
feature = clazz && @lang.non_containments(clazz.ecore).find{|f| f.name == fn}
|
60
|
+
if feature
|
61
|
+
if feature.is_a?(RGen::ECore::EReference)
|
62
|
+
if ref_completion_option_provider
|
63
|
+
ref_completion_option_provider.call(feature)
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
68
|
+
feature.eType.eLiterals.collect do |l|
|
69
|
+
CompletionOption.new("#{l.name}")
|
70
|
+
end
|
71
|
+
elsif feature.eType.instanceClass == String
|
72
|
+
[ CompletionOption.new("\"\"") ]
|
73
|
+
elsif feature.eType.instanceClass == Integer
|
74
|
+
(0..4).collect{|i| CompletionOption.new("#{i}") }
|
75
|
+
elsif feature.eType.instanceClass == Float
|
76
|
+
(0..4).collect{|i| CompletionOption.new("#{i}.0") }
|
77
|
+
elsif feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
|
78
|
+
[true, false].collect{|b| CompletionOption.new("#{b}") }
|
79
|
+
else
|
80
|
+
[]
|
81
|
+
end
|
82
|
+
else
|
83
|
+
[]
|
84
|
+
end
|
85
|
+
else
|
86
|
+
[]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def completion_classes(prev_line_provider)
|
93
|
+
clazz, feature = context(prev_line_provider)
|
94
|
+
if clazz
|
95
|
+
if feature
|
96
|
+
@lang.concrete_types(feature.eType)
|
97
|
+
else
|
98
|
+
refs_by_class = {}
|
99
|
+
clazz.eAllReferences.select{|r| r.containment}.each do |r|
|
100
|
+
@lang.concrete_types(r.eType).each { |c| (refs_by_class[c] ||= []) << r }
|
101
|
+
end
|
102
|
+
refs_by_class.keys.select{|c| refs_by_class[c].size == 1}
|
103
|
+
end
|
104
|
+
else
|
105
|
+
@lang.root_epackage.eAllClasses.select{|c| !c.abstract &&
|
106
|
+
!c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def context(prev_line_provider)
|
111
|
+
command, role = parse_context(prev_line_provider)
|
112
|
+
clazz = command && @lang.root_epackage.eAllClasses.find{|c| c.name == command}
|
113
|
+
feature = role && clazz && clazz.eAllReferences.find{|r| r.containment && r.name == role}
|
114
|
+
[clazz, feature]
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_context(prev_line_provider)
|
118
|
+
block_nesting = 0
|
119
|
+
array_nesting = 0
|
120
|
+
non_empty_lines = 0
|
121
|
+
role = nil
|
122
|
+
i = 0
|
123
|
+
while line = prev_line_provider.call(i+=1)
|
124
|
+
# empty or comment
|
125
|
+
next if line =~ /^\s*$/ || line =~ /^\s*#/
|
126
|
+
# role
|
127
|
+
if line =~ /^\s*(\w+):\s*$/
|
128
|
+
role = $1 if non_empty_lines == 0
|
129
|
+
# block open
|
130
|
+
elsif line =~ /^\s*(\S+).*\{\s*$/
|
131
|
+
block_nesting -= 1
|
132
|
+
return [$1, role] if block_nesting < 0
|
133
|
+
# block close
|
134
|
+
elsif line =~ /^\s*\}\s*$/
|
135
|
+
block_nesting += 1
|
136
|
+
# array open
|
137
|
+
elsif line =~ /^\s*(\w+):\s*\[\s*$/
|
138
|
+
array_nesting -= 1
|
139
|
+
role = $1 if array_nesting < 0
|
140
|
+
# array close
|
141
|
+
elsif line =~ /^\s*\]\s*$/
|
142
|
+
array_nesting += 1
|
143
|
+
end
|
144
|
+
non_empty_lines += 1
|
145
|
+
end
|
146
|
+
[nil, nil]
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|