| 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..db41603deb5fdcfa45d3209443aa615c84ac3a38
|
| --- /dev/null
|
| +++ b/server/static/rpcexplorer/rpc-completer.html
|
| @@ -0,0 +1,305 @@
|
| +<!--
|
| + 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},
|
| + 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) {
|
| + return [];
|
| + }
|
| +
|
| + // 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 '.'.
|
| + typeName = util.trimPrefixDot(typeName);
|
| + type = util.resolve(this.description, typeName);
|
| + if (!type) {
|
| + return [];
|
| + }
|
| + }
|
| +
|
| + // 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>
|
|
|