UNPKG

kibana-123

Version:

Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic

233 lines (191 loc) 7.02 kB
var _ = require('lodash'); var $ = require('jquery'); var grammar = require('raw!../chain.peg'); var PEG = require('pegjs'); var Parser = PEG.buildParser(grammar); var template = require('./partials/suggestion.html'); var app = require('ui/modules').get('apps/timelion', []); /* Autocomplete proposal, this file doesn't actually work like this function names Do not auto complete .sometext(, rather insert a closing ) whenever a ( is typed. .| (single dot) .func| argument names We’ll need to sort out which function we’re inside, must be inside a function though .function(|) // Suggest the first name aka most important arg, e.g. foo= .function(fo|) // Suggest foo= .function(foo=|) // Suggest [bar,baz] .function(arg=bar, |) Suggest 2nd arg name, and so on argument values Only named arguments, necessarily provided optional by a plugin. Must be inside a function, and start must be adjacent to the argument name .function(arg=b|) */ app.directive('timelionExpression', function ($compile, $http, $timeout, $rootScope, config) { return { restrict: 'A', require: 'ngModel', link: function ($scope, $elem, $attrs, ngModelCtrl) { var keys = { ESC: 27, UP: 38, DOWN: 40, TAB: 9, ENTER: 13 }; var functionReference = {}; function init() { resetSuggestions(); $elem.on('mouseup', function () { suggest($attrs.timelionExpression); digest(); }); $elem.on('keydown', keyDownHandler); $elem.on('keyup', keyUpHandler); $elem.on('blur', function () { $timeout(function () { $scope.suggestions.show = false; }, 100); }); $elem.after($compile(template)($scope)); $http.get('../api/timelion/functions').then(function (resp) { functionReference.byName = _.indexBy(resp.data, 'name'); functionReference.list = resp.data; }); } $scope.$on('$destroy', function () { $elem.off('mouseup'); $elem.off('keydown'); $elem.off('keyup'); $elem.off('blur'); }); function suggest(val) { try { // Inside an existing function providing suggestion only as a reference. Maybe suggest an argument? var possible = findFunction(getCaretPos(), Parser.parse(val).functions); // TODO: Reference suggestors. Only supporting completion right now; resetSuggestions(); return; if (functionReference.byName) { if (functionReference.byName[possible.function]) { $scope.suggestions.list = [functionReference.byName[possible.function]]; $scope.suggestions.show = true; } else { resetSuggestions(); } } } catch (e) { try { // Is this a structured exception? e = JSON.parse(e.message); if (e.location.min > getCaretPos() || e.location.max <= getCaretPos()) { resetSuggestions(); return; } // TODO: Abstract structured exception handling; if (e.type === 'incompleteFunction') { if (e.function == null) { $scope.suggestions.list = functionReference.list; } else { $scope.suggestions.list = _.compact(_.map(functionReference.list, function (func) { if (_.startsWith(func.name, e.function)) { return func; } })); } $scope.suggestions.show = true; } $scope.suggestions.location = e.location; } catch (e) { resetSuggestions(); } } digest(); } function validateSelection() { var maxSelection = $scope.suggestions.list.length - 1; if ($scope.suggestions.selected > maxSelection) $scope.suggestions.selected = maxSelection; else if ($scope.suggestions.selected < 0) $scope.suggestions.selected = 0; } $scope.completeExpression = function (selected) { if (!$scope.suggestions.list.length) return; var expression = $attrs.timelionExpression; var startOf = expression.slice(0, $scope.suggestions.location.min); var endOf = expression.slice($scope.suggestions.location.max, expression.length); var completeFunction = $scope.suggestions.list[selected].name + '()'; var newVal = startOf + completeFunction + endOf; $elem.val(newVal); $elem[0].selectionStart = $elem[0].selectionEnd = (startOf + completeFunction).length - 1; ngModelCtrl.$setViewValue(newVal); resetSuggestions(); }; function keyDownHandler(e) { if (_.contains(_.values(keys), e.keyCode)) e.preventDefault(); switch (e.keyCode) { case keys.UP: if ($scope.suggestions.selected > 0) $scope.suggestions.selected--; break; case keys.DOWN: $scope.suggestions.selected++; break; case keys.TAB: $scope.completeExpression($scope.suggestions.selected); break; case keys.ENTER: if ($scope.suggestions.list.length) { $scope.completeExpression($scope.suggestions.selected); } else { $elem.submit(); } break; case keys.ESC: $scope.suggestions.show = false; break; } scrollTo($scope.suggestions); digest(); } function keyUpHandler(e) { if (_.contains(_.values(keys), e.keyCode)) return; suggest($attrs.timelionExpression); validateSelection(); digest(); } function resetSuggestions() { $scope.suggestions = { selected: 0, list: [], position: {}, show: false }; return $scope.suggestions; } function scrollTo(suggestions) { validateSelection(); var suggestionsListElem = $('.suggestions'); var suggestedElem = $($('.suggestion')[suggestions.selected]); if (!suggestedElem.position() || !suggestedElem.position().top) return; suggestionsListElem.scrollTop(suggestionsListElem.scrollTop() + suggestedElem.position().top); } function findFunction(position, functionList) { var bestFunction; _.each(functionList, function (func) { if ((func.location.min) < position && position < (func.location.max)) { if (!bestFunction || func.text.length < bestFunction.text.length) { bestFunction = func; } } }); return bestFunction; }; function getCaretPos() { return $elem[0].selectionStart; }; function digest() { $rootScope.$$phase || $scope.$digest(); } init(); } }; });