Chromium Code Reviews| Index: server/static/rpcexplorer/rpc-completer.html |
| diff --git a/server/static/rpcexplorer/rpc-completer.html b/server/static/rpcexplorer/rpc-completer.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..35e0dec35bdb885f77c4ee2194956b3bbf0362f7 |
| --- /dev/null |
| +++ b/server/static/rpcexplorer/rpc-completer.html |
| @@ -0,0 +1,308 @@ |
| +<!-- |
| + Copyright 2016 The Chromium Authors. All rights reserved. |
| + Use of this source code is governed by a BSD-style license that can be |
| + found in the LICENSE file. |
| + --> |
| + |
| +<link rel="import" href="../bower_components/polymer/polymer.html"> |
| + |
| +<link rel="import" href="rpc-descriptor-util.html"> |
| + |
| +<!-- |
| + The `rpc-completer` element implements Ace editor completer interface |
| + based on a protobuf message descriptors. |
| +--> |
| +<script> |
| +'use strict'; |
| + |
| +Polymer({ |
| + is: 'rpc-completer', |
| + |
| + properties: { |
| + /** @type {FileDescriptorSet} */ |
| + description: Object, |
| + |
| + rootTypeName: String, |
| + |
| + /** @type {DescriptorProto} */ |
| + rootType: { |
| + type: Object, |
| + computed: '_resolveType(description, rootTypeName)' |
| + } |
| + }, |
| + |
| + |
| + /** |
| + * Returns elements to display in autocomplete. |
| + */ |
| + getCompletions: function(editor, session, pos, prefix, callback) { |
| + if (!this.rootType) { |
| + return; |
| + } |
| + |
| + // Get all text left to the current selection. |
| + var beforePos = { |
| + 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.
|
| + end: session.selection.getRange().start |
| + }; |
| + var text = session.getTextRange(beforePos); |
| + var completions = this.getCompletionsForText(this.rootType, text); |
| + if (completions) { |
| + callback(null, completions); |
| + } |
| + }, |
| + |
| + /** |
| + * Returns leading comments of a completion. |
| + * The result is displayed to the right of the selected completion. |
| + */ |
| + getDocTooltip: function(completion) { |
| + return completion.docTooltip; |
| + }, |
| + |
| + getCompletionsForText: function(type, text) { |
| + // Resolve path. |
| + var path = this.getCurrentPath(text); |
| + if (path == null) { |
| + 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
|
| + return []; |
| + } |
| + console.log('completion: current path: ', path); |
|
Bons
2016/02/23 15:52:28
remove?
nodir
2016/02/23 18:32:25
Done.
|
| + |
| + // Resolve type. |
| + var util = rpcExplorer.descUtil; |
| + for (var i = 0; i < path.length; i++) { |
| + if (type.type != 'messageType') { |
| + return []; |
| + } |
| + var field = util.findByName(type.desc.field, path[i]); |
| + if (!field) { |
| + console.log('Field ' + path[i] + ' not found') |
| + return []; |
| + } |
| + var typeName = field.type_name; |
| + if (typeof typeName != 'string') { |
| + console.log('Field ' + path[i] + ' is not a message or enum') |
| + return []; |
| + } |
| + // referenced types are often prefixed with '.' |
|
Bons
2016/02/23 15:52:28
Capitalize 'referenced'
nodir
2016/02/23 18:32:25
Done.
|
| + typeName = util.trimPrefixDot(typeName); |
| + type = util.resolve(this.description, typeName); |
| + if (!type) { |
| + return []; |
| + } |
| + } |
| + |
| + console.log('completion: current type: ', type.desc.name); |
|
Bons
2016/02/23 15:52:28
remove?
nodir
2016/02/23 18:32:25
Done.
|
| + |
| + // Automatically surround with quotes. |
| + var quoteCount = (text.match(/"/g) || []).length; |
| + var shouldQuote = quoteCount % 2 === 0; |
| + |
| + function docTooltip(desc) { |
| + var info = desc.source_code_info; |
| + return info && info.leading_comments || ''; |
| + } |
| + |
| + var completions = []; |
| + switch (type.type) { |
| + case 'messageType': |
| + if (!type.desc.field) { |
| + break; |
| + } |
| + for (var i = 0; i < type.desc.field.length; i++) { |
| + var field = type.desc.field[i]; |
| + var meta = this.fieldTypeName(field); |
| + if (field.label === 'LABEL_REPEATED') { |
| + meta = 'repeated ' + meta; |
| + } |
| + completions.push({ |
| + caption: field.name, |
| + snippet: this.snippetForField(field, shouldQuote), |
| + meta: meta, |
| + docTooltip: docTooltip(field) |
| + }); |
| + } |
| + break; |
| + |
| + case 'enumType': |
| + for (var i = 0; i < type.desc.value.length; i++) { |
| + var value = type.desc.value[i]; |
| + var snippet = value.name; |
| + if (shouldQuote) { |
| + snippet = '"' + snippet + '"'; |
| + } |
| + completions.push({ |
| + caption: value.name, |
| + snippet: snippet, |
| + meta: '' + value.number, |
| + docTooltip: docTooltip(value) |
| + }); |
| + } |
| + break; |
| + } |
| + return completions; |
| + }, |
| + |
| + snippetForField: function(field, shouldQuote) { |
| + // snippet docs: |
| + // https://cloud9-sdk.readme.io/docs/snippets |
| + var snippet = field.name; |
| + if (shouldQuote) { |
| + snippet = '"' + snippet + '"'; |
| + } |
| + if (!shouldQuote) { |
| + return snippet; |
| + } |
| + |
| + snippet += ': '; |
| + |
| + var open = ''; |
| + var close = ''; |
| + if (field.label === 'LABEL_REPEATED') { |
| + open += '['; |
| + close = ']' + close; |
| + } |
| + |
| + switch (field.type) { |
| + case 'TYPE_MESSAGE': |
| + open += '{'; |
| + close = '}' + close; |
| + break; |
| + case 'TYPE_STRING': |
| + case 'TYPE_ENUM': |
| + open += '"'; |
| + close = '"' + close; |
| + break; |
| + } |
| + |
| + // ${0} is the position of cursor after insertion. |
| + snippet += open + '${0}' + close; |
| + return snippet; |
| + }, |
| + |
| + /** |
| + * Resolves path at the end of text, best effort. |
| + * e.g. for text '{ "a": { "b": [' returns ['a', 'b'] |
| + * For '{ "a": {}, "b": {' returns ['b']. |
| + * For '{ "a":' returns ['a']. |
| + */ |
| + getCurrentPath: function(text) { |
| + var path = []; |
| + for (var i = 0; i < text.length;) { |
| + i = text.indexOf(':', i); |
| + if (i === -1) { |
| + break; |
| + } |
| + var colon = i; |
| + |
| + i++; |
| + i = this._skipWhitespace(text, i); |
| + |
| + if (i === text.length || text.charAt(i) === '"' && i+1 === text.length) { |
| + // the path is a field. |
| + } else if (text.charAt(i) in {'{':0, '[': 0}) { |
| + // there is an array or object after the colon |
| + var closingIndex = this.findMatching(text, i); |
| + if (closingIndex !== -1) { |
| + // Not an object/array or closed. Ignore. |
| + continue; |
| + } |
| + } else { |
| + continue |
| + } |
| + |
| + // read the name to the left of colon. |
| + var secondQuote = text.lastIndexOf('"', colon); |
| + if (secondQuote === -1) { |
| + return null; |
| + } |
| + |
| + var firstQuote = text.lastIndexOf('"', secondQuote - 1); |
| + if (firstQuote === -1) { |
| + return null; |
| + } |
| + |
| + path.push(text.substring(firstQuote + 1, secondQuote)); |
| + } |
| + return path; |
| + }, |
| + |
| + /** Finds index of the matching brace. */ |
| + findMatching: function(text, i) { |
| + var level = 0; |
| + var open = text.charAt(i); |
| + var close; |
| + switch (open) { |
| + case '{': |
| + close = '}'; |
| + break; |
| + |
| + case '[': |
| + close = ']'; |
| + break; |
| + |
| + default: |
| + throw Error('Unexpected brace: ' + open); |
| + } |
| + |
| + for (; i < text.length; i++) { |
| + switch (text.charAt(i)) { |
| + case open: |
| + level++; |
| + break; |
| + case close: |
| + level--; |
| + if (level === 0) { |
| + return i; |
| + } |
| + break; |
| + } |
| + } |
| + return -1; |
| + }, |
| + |
| + _resolveType: function(desc, name) { |
| + return rpcExplorer.descUtil.resolve(desc, name); |
| + }, |
| + |
| + _scalarTypeNames: { |
| + TYPE_DOUBLE: 'double', |
| + TYPE_FLOAT: 'float', |
| + TYPE_INT64: 'int64', |
| + TYPE_UINT64: 'uint64', |
| + TYPE_INT32: 'int32', |
| + TYPE_FIXED64: 'fixed64', |
| + TYPE_FIXED32: 'fixed32', |
| + TYPE_BOOL: 'bool', |
| + TYPE_STRING: 'string', |
| + TYPE_BYTES: 'bytes', |
| + TYPE_UINT32: 'uint32', |
| + TYPE_SFIXED32: 'sfixed32', |
| + TYPE_SFIXED64: 'sfixed64', |
| + TYPE_SINT32: 'sint32', |
| + TYPE_SINT64: 'sint64', |
| + }, |
| + |
| + fieldTypeName: function(field) { |
| + var name = this._scalarTypeNames[field.type]; |
| + if (!name) { |
| + name = rpcExplorer.descUtil.trimPrefixDot(field.type_name); |
| + } |
| + return name; |
| + }, |
| + |
| + _skipWhitespace: function(text, i) { |
| + var space = { |
| + ' ': 1, |
| + '\n': 1, |
| + '\r': 1, |
| + '\t': 1 |
| + }; |
| + while (space[text.charAt(i)]) { |
| + i++; |
| + } |
| + return i; |
| + } |
| +}); |
| +</script> |