OLD | NEW |
---|---|
(Empty) | |
1 <!-- | |
2 Copyright 2016 The Chromium Authors. All rights reserved. | |
3 Use of this source code is governed by a BSD-style license that can be | |
4 found in the LICENSE file. | |
5 --> | |
6 | |
7 <link rel="import" href="../bower_components/polymer/polymer.html"> | |
8 | |
9 <link rel="import" href="rpc-descriptor-util.html"> | |
10 | |
11 <!-- | |
12 The `rpc-completer` element implements Ace editor completer interface | |
13 based on a protobuf message descriptors. | |
14 --> | |
15 <script> | |
16 'use strict'; | |
17 | |
18 Polymer({ | |
19 is: 'rpc-completer', | |
20 | |
21 properties: { | |
22 /** @type {FileDescriptorSet} */ | |
23 description: Object, | |
24 | |
25 rootTypeName: String, | |
26 | |
27 /** @type {DescriptorProto} */ | |
28 rootType: { | |
29 type: Object, | |
30 computed: '_resolveType(description, rootTypeName)' | |
31 } | |
32 }, | |
33 | |
34 | |
35 /** | |
36 * Returns elements to display in autocomplete. | |
37 */ | |
38 getCompletions: function(editor, session, pos, prefix, callback) { | |
39 if (!this.rootType) { | |
40 return; | |
41 } | |
42 | |
43 // Get all text left to the current selection. | |
44 var beforePos = { | |
45 start: {row: 0, col: 0 }, | |
Bons
2016/02/23 15:52:28
extra space after 0. either do {row: 0, col: 0} or
nodir
2016/02/23 18:32:25
Done.
| |
46 end: session.selection.getRange().start | |
47 }; | |
48 var text = session.getTextRange(beforePos); | |
49 var completions = this.getCompletionsForText(this.rootType, text); | |
50 if (completions) { | |
51 callback(null, completions); | |
52 } | |
53 }, | |
54 | |
55 /** | |
56 * Returns leading comments of a completion. | |
57 * The result is displayed to the right of the selected completion. | |
58 */ | |
59 getDocTooltip: function(completion) { | |
60 return completion.docTooltip; | |
61 }, | |
62 | |
63 getCompletionsForText: function(type, text) { | |
64 // Resolve path. | |
65 var path = this.getCurrentPath(text); | |
66 if (path == null) { | |
67 console.log('completion: could not compute path'); | |
Bons
2016/02/23 15:52:28
console.error?
nodir
2016/02/23 18:32:25
removed it. It was rather for debugging, does not
| |
68 return []; | |
69 } | |
70 console.log('completion: current path: ', path); | |
Bons
2016/02/23 15:52:28
remove?
nodir
2016/02/23 18:32:25
Done.
| |
71 | |
72 // Resolve type. | |
73 var util = rpcExplorer.descUtil; | |
74 for (var i = 0; i < path.length; i++) { | |
75 if (type.type != 'messageType') { | |
76 return []; | |
77 } | |
78 var field = util.findByName(type.desc.field, path[i]); | |
79 if (!field) { | |
80 console.log('Field ' + path[i] + ' not found') | |
81 return []; | |
82 } | |
83 var typeName = field.type_name; | |
84 if (typeof typeName != 'string') { | |
85 console.log('Field ' + path[i] + ' is not a message or enum') | |
86 return []; | |
87 } | |
88 // referenced types are often prefixed with '.' | |
Bons
2016/02/23 15:52:28
Capitalize 'referenced'
nodir
2016/02/23 18:32:25
Done.
| |
89 typeName = util.trimPrefixDot(typeName); | |
90 type = util.resolve(this.description, typeName); | |
91 if (!type) { | |
92 return []; | |
93 } | |
94 } | |
95 | |
96 console.log('completion: current type: ', type.desc.name); | |
Bons
2016/02/23 15:52:28
remove?
nodir
2016/02/23 18:32:25
Done.
| |
97 | |
98 // Automatically surround with quotes. | |
99 var quoteCount = (text.match(/"/g) || []).length; | |
100 var shouldQuote = quoteCount % 2 === 0; | |
101 | |
102 function docTooltip(desc) { | |
103 var info = desc.source_code_info; | |
104 return info && info.leading_comments || ''; | |
105 } | |
106 | |
107 var completions = []; | |
108 switch (type.type) { | |
109 case 'messageType': | |
110 if (!type.desc.field) { | |
111 break; | |
112 } | |
113 for (var i = 0; i < type.desc.field.length; i++) { | |
114 var field = type.desc.field[i]; | |
115 var meta = this.fieldTypeName(field); | |
116 if (field.label === 'LABEL_REPEATED') { | |
117 meta = 'repeated ' + meta; | |
118 } | |
119 completions.push({ | |
120 caption: field.name, | |
121 snippet: this.snippetForField(field, shouldQuote), | |
122 meta: meta, | |
123 docTooltip: docTooltip(field) | |
124 }); | |
125 } | |
126 break; | |
127 | |
128 case 'enumType': | |
129 for (var i = 0; i < type.desc.value.length; i++) { | |
130 var value = type.desc.value[i]; | |
131 var snippet = value.name; | |
132 if (shouldQuote) { | |
133 snippet = '"' + snippet + '"'; | |
134 } | |
135 completions.push({ | |
136 caption: value.name, | |
137 snippet: snippet, | |
138 meta: '' + value.number, | |
139 docTooltip: docTooltip(value) | |
140 }); | |
141 } | |
142 break; | |
143 } | |
144 return completions; | |
145 }, | |
146 | |
147 snippetForField: function(field, shouldQuote) { | |
148 // snippet docs: | |
149 // https://cloud9-sdk.readme.io/docs/snippets | |
150 var snippet = field.name; | |
151 if (shouldQuote) { | |
152 snippet = '"' + snippet + '"'; | |
153 } | |
154 if (!shouldQuote) { | |
155 return snippet; | |
156 } | |
157 | |
158 snippet += ': '; | |
159 | |
160 var open = ''; | |
161 var close = ''; | |
162 if (field.label === 'LABEL_REPEATED') { | |
163 open += '['; | |
164 close = ']' + close; | |
165 } | |
166 | |
167 switch (field.type) { | |
168 case 'TYPE_MESSAGE': | |
169 open += '{'; | |
170 close = '}' + close; | |
171 break; | |
172 case 'TYPE_STRING': | |
173 case 'TYPE_ENUM': | |
174 open += '"'; | |
175 close = '"' + close; | |
176 break; | |
177 } | |
178 | |
179 // ${0} is the position of cursor after insertion. | |
180 snippet += open + '${0}' + close; | |
181 return snippet; | |
182 }, | |
183 | |
184 /** | |
185 * Resolves path at the end of text, best effort. | |
186 * e.g. for text '{ "a": { "b": [' returns ['a', 'b'] | |
187 * For '{ "a": {}, "b": {' returns ['b']. | |
188 * For '{ "a":' returns ['a']. | |
189 */ | |
190 getCurrentPath: function(text) { | |
191 var path = []; | |
192 for (var i = 0; i < text.length;) { | |
193 i = text.indexOf(':', i); | |
194 if (i === -1) { | |
195 break; | |
196 } | |
197 var colon = i; | |
198 | |
199 i++; | |
200 i = this._skipWhitespace(text, i); | |
201 | |
202 if (i === text.length || text.charAt(i) === '"' && i+1 === text.length) { | |
203 // the path is a field. | |
204 } else if (text.charAt(i) in {'{':0, '[': 0}) { | |
205 // there is an array or object after the colon | |
206 var closingIndex = this.findMatching(text, i); | |
207 if (closingIndex !== -1) { | |
208 // Not an object/array or closed. Ignore. | |
209 continue; | |
210 } | |
211 } else { | |
212 continue | |
213 } | |
214 | |
215 // read the name to the left of colon. | |
216 var secondQuote = text.lastIndexOf('"', colon); | |
217 if (secondQuote === -1) { | |
218 return null; | |
219 } | |
220 | |
221 var firstQuote = text.lastIndexOf('"', secondQuote - 1); | |
222 if (firstQuote === -1) { | |
223 return null; | |
224 } | |
225 | |
226 path.push(text.substring(firstQuote + 1, secondQuote)); | |
227 } | |
228 return path; | |
229 }, | |
230 | |
231 /** Finds index of the matching brace. */ | |
232 findMatching: function(text, i) { | |
233 var level = 0; | |
234 var open = text.charAt(i); | |
235 var close; | |
236 switch (open) { | |
237 case '{': | |
238 close = '}'; | |
239 break; | |
240 | |
241 case '[': | |
242 close = ']'; | |
243 break; | |
244 | |
245 default: | |
246 throw Error('Unexpected brace: ' + open); | |
247 } | |
248 | |
249 for (; i < text.length; i++) { | |
250 switch (text.charAt(i)) { | |
251 case open: | |
252 level++; | |
253 break; | |
254 case close: | |
255 level--; | |
256 if (level === 0) { | |
257 return i; | |
258 } | |
259 break; | |
260 } | |
261 } | |
262 return -1; | |
263 }, | |
264 | |
265 _resolveType: function(desc, name) { | |
266 return rpcExplorer.descUtil.resolve(desc, name); | |
267 }, | |
268 | |
269 _scalarTypeNames: { | |
270 TYPE_DOUBLE: 'double', | |
271 TYPE_FLOAT: 'float', | |
272 TYPE_INT64: 'int64', | |
273 TYPE_UINT64: 'uint64', | |
274 TYPE_INT32: 'int32', | |
275 TYPE_FIXED64: 'fixed64', | |
276 TYPE_FIXED32: 'fixed32', | |
277 TYPE_BOOL: 'bool', | |
278 TYPE_STRING: 'string', | |
279 TYPE_BYTES: 'bytes', | |
280 TYPE_UINT32: 'uint32', | |
281 TYPE_SFIXED32: 'sfixed32', | |
282 TYPE_SFIXED64: 'sfixed64', | |
283 TYPE_SINT32: 'sint32', | |
284 TYPE_SINT64: 'sint64', | |
285 }, | |
286 | |
287 fieldTypeName: function(field) { | |
288 var name = this._scalarTypeNames[field.type]; | |
289 if (!name) { | |
290 name = rpcExplorer.descUtil.trimPrefixDot(field.type_name); | |
291 } | |
292 return name; | |
293 }, | |
294 | |
295 _skipWhitespace: function(text, i) { | |
296 var space = { | |
297 ' ': 1, | |
298 '\n': 1, | |
299 '\r': 1, | |
300 '\t': 1 | |
301 }; | |
302 while (space[text.charAt(i)]) { | |
303 i++; | |
304 } | |
305 return i; | |
306 } | |
307 }); | |
308 </script> | |
OLD | NEW |