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 || |
| 199 text.charAt(i) === '"' && i+1 === text.length) { |
| 200 // the path is a field. |
| 201 } else if (text.charAt(i) in {'{':0, '[': 0}) { |
| 202 // there is an array or object after the colon |
| 203 var closingIndex = this.findMatching(text, i); |
| 204 if (closingIndex !== -1) { |
| 205 // Not an object/array or closed. Ignore. |
| 206 continue; |
| 207 } |
| 208 } else { |
| 209 continue |
| 210 } |
| 211 |
| 212 // read the name to the left of colon. |
| 213 var secondQuote = text.lastIndexOf('"', colon); |
| 214 if (secondQuote === -1) { |
| 215 return null; |
| 216 } |
| 217 |
| 218 var firstQuote = text.lastIndexOf('"', secondQuote - 1); |
| 219 if (firstQuote === -1) { |
| 220 return null; |
| 221 } |
| 222 |
| 223 path.push(text.substring(firstQuote + 1, secondQuote)); |
| 224 } |
| 225 return path; |
| 226 }, |
| 227 |
| 228 /** Finds index of the matching brace. */ |
| 229 findMatching: function(text, i) { |
| 230 var level = 0; |
| 231 var open = text.charAt(i); |
| 232 var close; |
| 233 switch (open) { |
| 234 case '{': |
| 235 close = '}'; |
| 236 break; |
| 237 |
| 238 case '[': |
| 239 close = ']'; |
| 240 break; |
| 241 |
| 242 default: |
| 243 throw Error('Unexpected brace: ' + open); |
| 244 } |
| 245 |
| 246 for (; i < text.length; i++) { |
| 247 switch (text.charAt(i)) { |
| 248 case open: |
| 249 level++; |
| 250 break; |
| 251 case close: |
| 252 level--; |
| 253 if (level === 0) { |
| 254 return i; |
| 255 } |
| 256 break; |
| 257 } |
| 258 } |
| 259 return -1; |
| 260 }, |
| 261 |
| 262 _resolveType: function(desc, name) { |
| 263 return rpcExplorer.descUtil.resolve(desc, name); |
| 264 }, |
| 265 |
| 266 _scalarTypeNames: { |
| 267 TYPE_DOUBLE: 'double', |
| 268 TYPE_FLOAT: 'float', |
| 269 TYPE_INT64: 'int64', |
| 270 TYPE_UINT64: 'uint64', |
| 271 TYPE_INT32: 'int32', |
| 272 TYPE_FIXED64: 'fixed64', |
| 273 TYPE_FIXED32: 'fixed32', |
| 274 TYPE_BOOL: 'bool', |
| 275 TYPE_STRING: 'string', |
| 276 TYPE_BYTES: 'bytes', |
| 277 TYPE_UINT32: 'uint32', |
| 278 TYPE_SFIXED32: 'sfixed32', |
| 279 TYPE_SFIXED64: 'sfixed64', |
| 280 TYPE_SINT32: 'sint32', |
| 281 TYPE_SINT64: 'sint64', |
| 282 }, |
| 283 |
| 284 fieldTypeName: function(field) { |
| 285 var name = this._scalarTypeNames[field.type]; |
| 286 if (!name) { |
| 287 name = rpcExplorer.descUtil.trimPrefixDot(field.type_name); |
| 288 } |
| 289 return name; |
| 290 }, |
| 291 |
| 292 _skipWhitespace: function(text, i) { |
| 293 var space = { |
| 294 ' ': 1, |
| 295 '\n': 1, |
| 296 '\r': 1, |
| 297 '\t': 1 |
| 298 }; |
| 299 while (space[text.charAt(i)]) { |
| 300 i++; |
| 301 } |
| 302 return i; |
| 303 } |
| 304 }); |
| 305 </script> |
OLD | NEW |