Chromium Code Reviews| 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 |