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}, | |
| 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 return []; | |
| 68 } | |
| 69 | |
| 70 // Resolve type. | |
| 71 var util = rpcExplorer.descUtil; | |
| 72 for (var i = 0; i < path.length; i++) { | |
| 73 if (type.type != 'messageType') { | |
| 74 return []; | |
| 75 } | |
| 76 var field = util.findByName(type.desc.field, path[i]); | |
| 77 if (!field) { | |
| 78 console.log('Field ' + path[i] + ' not found') | |
| 79 return []; | |
| 80 } | |
| 81 var typeName = field.type_name; | |
| 82 if (typeof typeName != 'string') { | |
| 83 console.log('Field ' + path[i] + ' is not a message or enum') | |
| 84 return []; | |
| 85 } | |
| 86 // Referenced types are often prefixed with '.'. | |
| 87 typeName = util.trimPrefixDot(typeName); | |
| 88 type = util.resolve(this.description, typeName); | |
| 89 if (!type) { | |
| 90 return []; | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 // Automatically surround with quotes. | |
| 95 var quoteCount = (text.match(/"/g) || []).length; | |
| 96 var shouldQuote = quoteCount % 2 === 0; | |
| 97 | |
| 98 function docTooltip(desc) { | |
| 99 var info = desc.source_code_info; | |
| 100 return info && info.leading_comments || ''; | |
| 101 } | |
| 102 | |
| 103 var completions = []; | |
| 104 switch (type.type) { | |
| 105 case 'messageType': | |
| 106 if (!type.desc.field) { | |
| 107 break; | |
| 108 } | |
| 109 for (var i = 0; i < type.desc.field.length; i++) { | |
| 110 var field = type.desc.field[i]; | |
| 111 var meta = this.fieldTypeName(field); | |
| 112 if (field.label === 'LABEL_REPEATED') { | |
| 113 meta = 'repeated ' + meta; | |
| 114 } | |
| 115 completions.push({ | |
| 116 caption: field.name, | |
| 117 snippet: this.snippetForField(field, shouldQuote), | |
| 118 meta: meta, | |
| 119 docTooltip: docTooltip(field) | |
| 120 }); | |
| 121 } | |
| 122 break; | |
| 123 | |
| 124 case 'enumType': | |
| 125 for (var i = 0; i < type.desc.value.length; i++) { | |
| 126 var value = type.desc.value[i]; | |
| 127 var snippet = value.name; | |
| 128 if (shouldQuote) { | |
| 129 snippet = '"' + snippet + '"'; | |
| 130 } | |
| 131 completions.push({ | |
| 132 caption: value.name, | |
| 133 snippet: snippet, | |
| 134 meta: '' + value.number, | |
| 135 docTooltip: docTooltip(value) | |
| 136 }); | |
| 137 } | |
| 138 break; | |
| 139 } | |
| 140 return completions; | |
| 141 }, | |
| 142 | |
| 143 snippetForField: function(field, shouldQuote) { | |
| 144 // snippet docs: | |
| 145 // https://cloud9-sdk.readme.io/docs/snippets | |
| 146 var snippet = field.name; | |
| 147 if (shouldQuote) { | |
| 148 snippet = '"' + snippet + '"'; | |
| 149 } | |
| 150 if (!shouldQuote) { | |
| 151 return snippet; | |
| 152 } | |
| 153 | |
| 154 snippet += ': '; | |
| 155 | |
| 156 var open = ''; | |
| 157 var close = ''; | |
| 158 if (field.label === 'LABEL_REPEATED') { | |
| 159 open += '['; | |
| 160 close = ']' + close; | |
| 161 } | |
| 162 | |
| 163 switch (field.type) { | |
| 164 case 'TYPE_MESSAGE': | |
| 165 open += '{'; | |
| 166 close = '}' + close; | |
| 167 break; | |
| 168 case 'TYPE_STRING': | |
| 169 case 'TYPE_ENUM': | |
| 170 open += '"'; | |
| 171 close = '"' + close; | |
| 172 break; | |
| 173 } | |
| 174 | |
| 175 // ${0} is the position of cursor after insertion. | |
| 176 snippet += open + '${0}' + close; | |
| 177 return snippet; | |
| 178 }, | |
| 179 | |
| 180 /** | |
| 181 * Resolves path at the end of text, best effort. | |
| 182 * e.g. for text '{ "a": { "b": [' returns ['a', 'b'] | |
| 183 * For '{ "a": {}, "b": {' returns ['b']. | |
| 184 * For '{ "a":' returns ['a']. | |
| 185 */ | |
| 186 getCurrentPath: function(text) { | |
| 187 var path = []; | |
| 188 for (var i = 0; i < text.length;) { | |
| 189 i = text.indexOf(':', i); | |
| 190 if (i === -1) { | |
| 191 break; | |
| 192 } | |
| 193 var colon = i; | |
| 194 | |
| 195 i++; | |
| 196 i = this._skipWhitespace(text, i); | |
| 197 | |
| 198 if (i === text.length || text.charAt(i) === '"' && i+1 === text.length) { | |
|
Bons
2016/02/24 06:10:56
>80 cols
nodir
2016/02/24 18:06:15
Done.
| |
| 199 // the path is a field. | |
| 200 } else if (text.charAt(i) in {'{':0, '[': 0}) { | |
| 201 // there is an array or object after the colon | |
| 202 var closingIndex = this.findMatching(text, i); | |
| 203 if (closingIndex !== -1) { | |
| 204 // Not an object/array or closed. Ignore. | |
| 205 continue; | |
| 206 } | |
| 207 } else { | |
| 208 continue | |
| 209 } | |
| 210 | |
| 211 // read the name to the left of colon. | |
| 212 var secondQuote = text.lastIndexOf('"', colon); | |
| 213 if (secondQuote === -1) { | |
| 214 return null; | |
| 215 } | |
| 216 | |
| 217 var firstQuote = text.lastIndexOf('"', secondQuote - 1); | |
| 218 if (firstQuote === -1) { | |
| 219 return null; | |
| 220 } | |
| 221 | |
| 222 path.push(text.substring(firstQuote + 1, secondQuote)); | |
| 223 } | |
| 224 return path; | |
| 225 }, | |
| 226 | |
| 227 /** Finds index of the matching brace. */ | |
| 228 findMatching: function(text, i) { | |
| 229 var level = 0; | |
| 230 var open = text.charAt(i); | |
| 231 var close; | |
| 232 switch (open) { | |
| 233 case '{': | |
| 234 close = '}'; | |
| 235 break; | |
| 236 | |
| 237 case '[': | |
| 238 close = ']'; | |
| 239 break; | |
| 240 | |
| 241 default: | |
| 242 throw Error('Unexpected brace: ' + open); | |
| 243 } | |
| 244 | |
| 245 for (; i < text.length; i++) { | |
| 246 switch (text.charAt(i)) { | |
| 247 case open: | |
| 248 level++; | |
| 249 break; | |
| 250 case close: | |
| 251 level--; | |
| 252 if (level === 0) { | |
| 253 return i; | |
| 254 } | |
| 255 break; | |
| 256 } | |
| 257 } | |
| 258 return -1; | |
| 259 }, | |
| 260 | |
| 261 _resolveType: function(desc, name) { | |
| 262 return rpcExplorer.descUtil.resolve(desc, name); | |
| 263 }, | |
| 264 | |
| 265 _scalarTypeNames: { | |
| 266 TYPE_DOUBLE: 'double', | |
| 267 TYPE_FLOAT: 'float', | |
| 268 TYPE_INT64: 'int64', | |
| 269 TYPE_UINT64: 'uint64', | |
| 270 TYPE_INT32: 'int32', | |
| 271 TYPE_FIXED64: 'fixed64', | |
| 272 TYPE_FIXED32: 'fixed32', | |
| 273 TYPE_BOOL: 'bool', | |
| 274 TYPE_STRING: 'string', | |
| 275 TYPE_BYTES: 'bytes', | |
| 276 TYPE_UINT32: 'uint32', | |
| 277 TYPE_SFIXED32: 'sfixed32', | |
| 278 TYPE_SFIXED64: 'sfixed64', | |
| 279 TYPE_SINT32: 'sint32', | |
| 280 TYPE_SINT64: 'sint64', | |
| 281 }, | |
| 282 | |
| 283 fieldTypeName: function(field) { | |
| 284 var name = this._scalarTypeNames[field.type]; | |
| 285 if (!name) { | |
| 286 name = rpcExplorer.descUtil.trimPrefixDot(field.type_name); | |
| 287 } | |
| 288 return name; | |
| 289 }, | |
| 290 | |
| 291 _skipWhitespace: function(text, i) { | |
| 292 var space = { | |
| 293 ' ': 1, | |
| 294 '\n': 1, | |
| 295 '\r': 1, | |
| 296 '\t': 1 | |
| 297 }; | |
| 298 while (space[text.charAt(i)]) { | |
| 299 i++; | |
| 300 } | |
| 301 return i; | |
| 302 } | |
| 303 }); | |
| 304 </script> | |
| OLD | NEW |