|
|
@@ -27,10 +27,13 @@ from desktop.views import _ko
|
|
|
width: 500px;
|
|
|
height: 250px;
|
|
|
border: 1px solid #DDD;
|
|
|
- border-radius: 3px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
+
|
|
|
+ border-radius: 2px;
|
|
|
background-color: #FFF;
|
|
|
+ -webkit-box-shadow: 0 2px 8px rgba(0,0,0,.18);
|
|
|
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18), 0 2px 8px 0 rgba(0, 0, 0, 0.13);
|
|
|
}
|
|
|
|
|
|
.autocompleter-header {
|
|
|
@@ -39,25 +42,27 @@ from desktop.views import _ko
|
|
|
}
|
|
|
|
|
|
.autocompleter-list {
|
|
|
+ position: relative;
|
|
|
flex: 1 1 100%;
|
|
|
overflow-y: auto;
|
|
|
- padding: 0 5px;
|
|
|
}
|
|
|
|
|
|
.autocompleter-list > div {
|
|
|
height: 19px;
|
|
|
clear: both;
|
|
|
+ background-color: #FFF;
|
|
|
+ padding:3px;
|
|
|
}
|
|
|
|
|
|
.autocompleter-list > div.selected {
|
|
|
- background-color: #F00;
|
|
|
+ background-color: #DBE8F1;
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
<script type="text/html" id="hue-ace-autocompleter">
|
|
|
<div class="autocompleter-header"><div class="pull-right">header</div></div>
|
|
|
- <div class="autocompleter-list" data-bind="foreach: activeSuggestions">
|
|
|
- <div data-bind="css: { 'selected': $index() === $parent.selectedIndex() }"><div class="pull-left" data-bind="text: value"></div><div class="pull-right" data-bind="text: meta"></div></div>
|
|
|
+ <div class="autocompleter-list" data-bind="foreach: filteredSuggestions">
|
|
|
+ <div data-bind="css: { 'selected': $index() === $parent.selectedIndex() }"><div class="pull-left" data-bind="matchedText: { suggestion: $data, filter: $parent.prefixFilter }"></div><div class="pull-right" data-bind="text: meta"></div></div>
|
|
|
</div>
|
|
|
<!-- /ko -->
|
|
|
</script>
|
|
|
@@ -67,6 +72,25 @@ from desktop.views import _ko
|
|
|
var aceUtil = ace.require("ace/autocomplete/util");
|
|
|
var HashHandler = ace.require("ace/keyboard/hash_handler").HashHandler;
|
|
|
|
|
|
+ ko.bindingHandlers.matchedText = {
|
|
|
+ update: function (element, valueAccessor) {
|
|
|
+ var options = valueAccessor();
|
|
|
+ var $element = $(element);
|
|
|
+ $element.empty()
|
|
|
+ var suggestion = options.suggestion;
|
|
|
+ if (options.filter() && suggestion.matchIndex > -1) {
|
|
|
+ var before = suggestion.value.substring(0, suggestion.matchIndex);
|
|
|
+ var match = suggestion.value.substring(suggestion.matchIndex, suggestion.matchIndex + suggestion.matchLength);
|
|
|
+ var after = suggestion.value.substring(suggestion.matchIndex + suggestion.matchLength);
|
|
|
+ $element.append(document.createTextNode(before));
|
|
|
+ $('<b>').text(match).appendTo($element);
|
|
|
+ $element.append(document.createTextNode(after));
|
|
|
+ } else {
|
|
|
+ $element.text(suggestion.value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
function HueAceAutocompleter (params, element) {
|
|
|
var self = this;
|
|
|
var snippet = params.snippet;
|
|
|
@@ -74,6 +98,16 @@ from desktop.views import _ko
|
|
|
|
|
|
var autocompleter = new SqlAutocompleter3({ snippet: snippet, timeout: AUTOCOMPLETE_TIMEOUT });
|
|
|
self.suggestions = autocompleter.autocomplete(editor.getTextBeforeCursor(), editor.getTextAfterCursor());
|
|
|
+
|
|
|
+ var session = editor.getSession();
|
|
|
+ var pos = editor.getCursorPosition();
|
|
|
+
|
|
|
+ var line = session.getLine(pos.row);
|
|
|
+ var prefix = aceUtil.retrievePrecedingIdentifier(line, pos.column);
|
|
|
+ self.base = session.doc.createAnchor(pos.row, pos.column - prefix.length);
|
|
|
+ self.base.$insertRight = true;
|
|
|
+
|
|
|
+ self.prefixFilter = ko.observable(prefix.toLowerCase());
|
|
|
self.activeSuggestions = ko.pureComputed(function () {
|
|
|
if (self.suggestions.keywords) {
|
|
|
return self.suggestions.keywords.suggestions();
|
|
|
@@ -81,40 +115,87 @@ from desktop.views import _ko
|
|
|
return [];
|
|
|
});
|
|
|
|
|
|
- self.selectedIndex = ko.observable(0);
|
|
|
+ self.filteredSuggestions = ko.pureComputed(function () {
|
|
|
+ if (self.prefixFilter()) {
|
|
|
+ var result = self.activeSuggestions().filter(function (suggestion) {
|
|
|
+ // TODO: Extend with fuzzy matches
|
|
|
+ var foundIndex = suggestion.value.toLowerCase().indexOf(self.prefixFilter());
|
|
|
+ if (foundIndex === 0) {
|
|
|
+ suggestion.sortWeight = 2;
|
|
|
+ } else if (foundIndex > 0) {
|
|
|
+ suggestion.sortWeight = 1;
|
|
|
+ }
|
|
|
+ suggestion.matchIndex = foundIndex;
|
|
|
+ suggestion.matchLength = self.prefixFilter().length;
|
|
|
+ return suggestion.value.toLowerCase().indexOf(self.prefixFilter()) !== -1;
|
|
|
+ });
|
|
|
|
|
|
- var session = editor.getSession();
|
|
|
- var pos = editor.getCursorPosition();
|
|
|
+ result.sort(function (a, b) {
|
|
|
+ if (typeof a.sortWeight !== 'undefined' && typeof b.sortWeight !== 'undefined' && b.sortWeight !== a.sortWeight) {
|
|
|
+ return b.sortWeight - a.sortWeight;
|
|
|
+ }
|
|
|
+ if (typeof a.sortWeight !== 'undefined' && typeof b.sortWeight === 'undefined') {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (typeof b.sortWeight !== 'undefined' && typeof a.sortWeight === 'undefined') {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ if (typeof a.weight !== 'undefined' && typeof b.weight !== 'undefined' && b.weight !== a.weight) {
|
|
|
+ return b.weight - a.weight;
|
|
|
+ }
|
|
|
+ if (typeof a.weight !== 'undefined' && typeof b.weight === 'undefined') {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (typeof b.weight !== 'undefined' && typeof a.weight === 'undefined') {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return a.value.localeCompare(b.value);
|
|
|
+ });
|
|
|
+ if (self.selectedIndex() > result.length - 1) {
|
|
|
+ self.selectedIndex(result.length === 0 ? 0 : result.length - 1);
|
|
|
+ }
|
|
|
|
|
|
- var line = session.getLine(pos.row);
|
|
|
- var prefix = aceUtil.retrievePrecedingIdentifier(line, pos.column);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ return self.activeSuggestions();
|
|
|
+ });
|
|
|
|
|
|
- self.base = session.doc.createAnchor(pos.row, pos.column - prefix.length);
|
|
|
- self.base.$insertRight = true;
|
|
|
+ self.selectedIndex = ko.observable(0);
|
|
|
|
|
|
self.keyboardHandler = new HashHandler();
|
|
|
self.keyboardHandler.bindKeys({
|
|
|
- "Up": function(editor) {
|
|
|
+ "Up": function() {
|
|
|
if (self.selectedIndex() !== 0) {
|
|
|
self.selectedIndex(self.selectedIndex() - 1);
|
|
|
}
|
|
|
+ self.scrollSelectionIntoView();
|
|
|
},
|
|
|
- "Down": function(editor) {
|
|
|
- if (self.selectedIndex() !== self.activeSuggestions().length) {
|
|
|
+ "Down": function() {
|
|
|
+ if (self.selectedIndex() !== self.filteredSuggestions().length - 1) {
|
|
|
self.selectedIndex(self.selectedIndex() + 1);
|
|
|
}
|
|
|
+ self.scrollSelectionIntoView();
|
|
|
},
|
|
|
- "Ctrl-Up|Ctrl-Home": function(editor) {
|
|
|
- console.log('start');
|
|
|
+ "Ctrl-Up|Ctrl-Home": function() {
|
|
|
+ self.selectedIndex(0);
|
|
|
+ self.scrollSelectionIntoView();
|
|
|
},
|
|
|
- "Ctrl-Down|Ctrl-End": function(editor) {
|
|
|
- console.log('end');
|
|
|
+ "Ctrl-Down|Ctrl-End": function() {
|
|
|
+ self.selectedIndex(self.filteredSuggestions().length - 1);
|
|
|
+ self.scrollSelectionIntoView();
|
|
|
},
|
|
|
"Esc": function(editor) {
|
|
|
self.destroy();
|
|
|
},
|
|
|
"Return": function(editor) {
|
|
|
- editor.execCommand("insertstring", self.activeSuggestions()[self.selectedIndex()].value);
|
|
|
+ if (self.prefixFilter()) {
|
|
|
+ var ranges = editor.selection.getAllRanges();
|
|
|
+ for (var i = 0, range; range = ranges[i]; i++) {
|
|
|
+ range.start.column -= self.prefixFilter().length;
|
|
|
+ editor.session.remove(range);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ editor.execCommand("insertstring", self.filteredSuggestions()[self.selectedIndex()].value);
|
|
|
editor.renderer.scrollCursorIntoView();
|
|
|
self.destroy();
|
|
|
},
|
|
|
@@ -140,7 +221,9 @@ from desktop.views import _ko
|
|
|
if (cursor.row != self.base.row || cursor.column < self.base.column) {
|
|
|
self.destroy();
|
|
|
} else {
|
|
|
- changeTimeout = window.setTimeout(self.updateCompletions, 200)
|
|
|
+ changeTimeout = window.setTimeout(function () {
|
|
|
+ self.prefixFilter(editor.session.getTextRange({ start: self.base, end: editor.getCursorPosition() }).toLowerCase())
|
|
|
+ }, 200);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -175,6 +258,24 @@ from desktop.views import _ko
|
|
|
editor.on("mousewheel", self.mousewheelListener);
|
|
|
}
|
|
|
|
|
|
+ HueAceAutocompleter.prototype.scrollSelectionIntoView = function () {
|
|
|
+ var self = this;
|
|
|
+ var $autocompleterList = $('.autocompleter-list');
|
|
|
+ var selected = $autocompleterList.children().eq(self.selectedIndex());
|
|
|
+ var selectedTop = selected.position().top;
|
|
|
+ if (selectedTop < 0) {
|
|
|
+ $autocompleterList.scrollTop($autocompleterList.scrollTop() + selectedTop);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var selectedHeight = selected.outerHeight(true);
|
|
|
+ var listHeight = $autocompleterList.innerHeight();
|
|
|
+
|
|
|
+ var diff = (selectedHeight + selectedTop) - listHeight;
|
|
|
+ if (diff > 0) {
|
|
|
+ $autocompleterList.scrollTop($autocompleterList.scrollTop() + diff);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
ko.components.register('hueAceAutocompleter', {
|
|
|
viewModel: { createViewModel: function (params, componentInfo) {
|
|
|
return new HueAceAutocompleter(params, componentInfo.element);
|