|
|
@@ -0,0 +1,1000 @@
|
|
|
+// Licensed to Cloudera, Inc. under one
|
|
|
+// or more contributor license agreements. See the NOTICE file
|
|
|
+// distributed with this work for additional information
|
|
|
+// regarding copyright ownership. Cloudera, Inc. licenses this file
|
|
|
+// to you under the Apache License, Version 2.0 (the
|
|
|
+// "License"); you may not use this file except in compliance
|
|
|
+// with the License. You may obtain a copy of the License at
|
|
|
+//
|
|
|
+// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+//
|
|
|
+// Unless required by applicable law or agreed to in writing, software
|
|
|
+// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+// See the License for the specific language governing permissions and
|
|
|
+// limitations under the License.
|
|
|
+
|
|
|
+import $ from 'jquery';
|
|
|
+import ko from 'knockout';
|
|
|
+
|
|
|
+import apiHelper from 'api/apiHelper';
|
|
|
+import AceLocationHandler from 'sql/aceLocationHandler';
|
|
|
+import huePubSub from 'utils/huePubSub';
|
|
|
+import sqlUtils from 'sql/sqlUtils';
|
|
|
+
|
|
|
+// TODO: Depends on Ace
|
|
|
+
|
|
|
+ko.bindingHandlers.aceEditor = {
|
|
|
+ init: function(element, valueAccessor) {
|
|
|
+ const $el = $(element);
|
|
|
+ const options = ko.unwrap(valueAccessor());
|
|
|
+ const snippet = options.snippet;
|
|
|
+ const aceOptions = options.aceOptions || {};
|
|
|
+
|
|
|
+ const disposeFunctions = [];
|
|
|
+
|
|
|
+ const dispose = function() {
|
|
|
+ disposeFunctions.forEach(dispose => {
|
|
|
+ dispose();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ ko.utils.domNodeDisposal.addDisposeCallback(element, dispose);
|
|
|
+
|
|
|
+ $el.text(snippet.statement_raw());
|
|
|
+
|
|
|
+ const editor = ace.edit($el.attr('id'));
|
|
|
+ const AceRange = ace.require('ace/range').Range;
|
|
|
+
|
|
|
+ const resizeAce = function() {
|
|
|
+ window.setTimeout(() => {
|
|
|
+ try {
|
|
|
+ editor.resize(true);
|
|
|
+ } catch (e) {
|
|
|
+ // Can happen when the editor hasn't been initialized
|
|
|
+ }
|
|
|
+ }, 0);
|
|
|
+ };
|
|
|
+
|
|
|
+ const assistToggleSub = huePubSub.subscribe('assist.set.manual.visibility', resizeAce);
|
|
|
+ const resizePubSub = huePubSub.subscribe('split.panel.resized', resizeAce);
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ assistToggleSub.remove();
|
|
|
+ resizePubSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const aceLocationHandler = new AceLocationHandler({
|
|
|
+ editor: editor,
|
|
|
+ editorId: $el.attr('id'),
|
|
|
+ snippet: snippet,
|
|
|
+ i18n: { expandStar: options.expandStar, contextTooltip: options.contextTooltip }
|
|
|
+ });
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ aceLocationHandler.dispose();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.session.setMode(snippet.getAceMode());
|
|
|
+ editor.setOptions({
|
|
|
+ fontSize: apiHelper.getFromTotalStorage(
|
|
|
+ 'hue.ace',
|
|
|
+ 'fontSize',
|
|
|
+ navigator.platform && navigator.platform.toLowerCase().indexOf('linux') > -1
|
|
|
+ ? '14px'
|
|
|
+ : '12px'
|
|
|
+ )
|
|
|
+ });
|
|
|
+
|
|
|
+ function processErrorsAndWarnings(type, list) {
|
|
|
+ editor.clearErrorsAndWarnings(type);
|
|
|
+ let offset = 0;
|
|
|
+ if (snippet.isSqlDialect() && editor.getSelectedText()) {
|
|
|
+ const selectionRange = editor.getSelectionRange();
|
|
|
+ offset = Math.min(selectionRange.start.row, selectionRange.end.row);
|
|
|
+ }
|
|
|
+ if (list.length > 0) {
|
|
|
+ list.forEach((item, cnt) => {
|
|
|
+ if (item.line !== null) {
|
|
|
+ if (type === 'error') {
|
|
|
+ editor.addError(item.message, item.line + offset);
|
|
|
+ } else {
|
|
|
+ editor.addWarning(item.message, item.line + offset);
|
|
|
+ }
|
|
|
+ if (cnt === 0) {
|
|
|
+ editor.scrollToLine(item.line + offset, true, true, () => {});
|
|
|
+ if (item.col !== null) {
|
|
|
+ editor.renderer.scrollCursorIntoView(
|
|
|
+ { row: item.line + offset, column: item.col + 10 },
|
|
|
+ 0.5
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const errorsSub = snippet.errors.subscribe(newErrors => {
|
|
|
+ processErrorsAndWarnings('error', newErrors);
|
|
|
+ });
|
|
|
+
|
|
|
+ const aceWarningsSub = snippet.aceWarnings.subscribe(newWarnings => {
|
|
|
+ processErrorsAndWarnings('warning', newWarnings);
|
|
|
+ });
|
|
|
+
|
|
|
+ const aceErrorsSub = snippet.aceErrors.subscribe(newErrors => {
|
|
|
+ processErrorsAndWarnings('error', newErrors);
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ errorsSub.dispose();
|
|
|
+ aceWarningsSub.dispose();
|
|
|
+ aceErrorsSub.dispose();
|
|
|
+ });
|
|
|
+
|
|
|
+ let darkThemeEnabled = apiHelper.getFromTotalStorage('ace', 'dark.theme.enabled', false);
|
|
|
+ editor.setTheme(darkThemeEnabled ? 'ace/theme/hue_dark' : 'ace/theme/hue');
|
|
|
+
|
|
|
+ const editorOptions = {
|
|
|
+ enableSnippets: true,
|
|
|
+ showGutter: false,
|
|
|
+ showLineNumbers: false,
|
|
|
+ showPrintMargin: false,
|
|
|
+ scrollPastEnd: 0.1,
|
|
|
+ minLines: 1,
|
|
|
+ maxLines: 25
|
|
|
+ };
|
|
|
+
|
|
|
+ editor.enabledMenuOptions = {
|
|
|
+ setShowInvisibles: true,
|
|
|
+ setTabSize: true,
|
|
|
+ setShowGutter: true
|
|
|
+ };
|
|
|
+
|
|
|
+ editor.customMenuOptions = {
|
|
|
+ setEnableDarkTheme: function(enabled) {
|
|
|
+ darkThemeEnabled = enabled;
|
|
|
+ apiHelper.setInTotalStorage('ace', 'dark.theme.enabled', darkThemeEnabled);
|
|
|
+ editor.setTheme(darkThemeEnabled ? 'ace/theme/hue_dark' : 'ace/theme/hue');
|
|
|
+ },
|
|
|
+ getEnableDarkTheme: function() {
|
|
|
+ return darkThemeEnabled;
|
|
|
+ },
|
|
|
+ setEnableAutocompleter: function(enabled) {
|
|
|
+ editor.setOption('enableBasicAutocompletion', enabled);
|
|
|
+ apiHelper.setInTotalStorage('hue.ace', 'enableBasicAutocompletion', enabled);
|
|
|
+ const $enableLiveAutocompletionChecked = $('#setEnableLiveAutocompletion:checked');
|
|
|
+ const $setEnableLiveAutocompletion = $('#setEnableLiveAutocompletion');
|
|
|
+ if (enabled && $enableLiveAutocompletionChecked.length === 0) {
|
|
|
+ $setEnableLiveAutocompletion.trigger('click');
|
|
|
+ } else if (!enabled && $enableLiveAutocompletionChecked.length !== 0) {
|
|
|
+ $setEnableLiveAutocompletion.trigger('click');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getEnableAutocompleter: function() {
|
|
|
+ return editor.getOption('enableBasicAutocompletion');
|
|
|
+ },
|
|
|
+ setEnableLiveAutocompletion: function(enabled) {
|
|
|
+ editor.setOption('enableLiveAutocompletion', enabled);
|
|
|
+ apiHelper.setInTotalStorage('hue.ace', 'enableLiveAutocompletion', enabled);
|
|
|
+ if (enabled && $('#setEnableAutocompleter:checked').length === 0) {
|
|
|
+ $('#setEnableAutocompleter').trigger('click');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getEnableLiveAutocompletion: function() {
|
|
|
+ return editor.getOption('enableLiveAutocompletion');
|
|
|
+ },
|
|
|
+ setFontSize: function(size) {
|
|
|
+ if (size.toLowerCase().indexOf('px') === -1 && size.toLowerCase().indexOf('em') === -1) {
|
|
|
+ size += 'px';
|
|
|
+ }
|
|
|
+ editor.setOption('fontSize', size);
|
|
|
+ apiHelper.setInTotalStorage('hue.ace', 'fontSize', size);
|
|
|
+ },
|
|
|
+ getFontSize: function() {
|
|
|
+ let size = editor.getOption('fontSize');
|
|
|
+ if (size.toLowerCase().indexOf('px') === -1 && size.toLowerCase().indexOf('em') === -1) {
|
|
|
+ size += 'px';
|
|
|
+ }
|
|
|
+ return size;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (window.ENABLE_SQL_SYNTAX_CHECK && window.Worker) {
|
|
|
+ let errorHighlightingEnabled = apiHelper.getFromTotalStorage(
|
|
|
+ 'hue.ace',
|
|
|
+ 'errorHighlightingEnabled',
|
|
|
+ true
|
|
|
+ );
|
|
|
+
|
|
|
+ if (errorHighlightingEnabled) {
|
|
|
+ aceLocationHandler.attachSqlSyntaxWorker();
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.customMenuOptions.setErrorHighlighting = function(enabled) {
|
|
|
+ errorHighlightingEnabled = enabled;
|
|
|
+ apiHelper.setInTotalStorage('hue.ace', 'errorHighlightingEnabled', enabled);
|
|
|
+ if (enabled) {
|
|
|
+ aceLocationHandler.attachSqlSyntaxWorker();
|
|
|
+ } else {
|
|
|
+ aceLocationHandler.detachSqlSyntaxWorker();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ editor.customMenuOptions.getErrorHighlighting = function() {
|
|
|
+ return errorHighlightingEnabled;
|
|
|
+ };
|
|
|
+ editor.customMenuOptions.setClearIgnoredSyntaxChecks = function() {
|
|
|
+ apiHelper.setInTotalStorage('hue.syntax.checker', 'suppressedRules', {});
|
|
|
+ $('#setClearIgnoredSyntaxChecks')
|
|
|
+ .hide()
|
|
|
+ .before('<div style="margin-top:5px;float:right;">done</div>');
|
|
|
+ };
|
|
|
+ editor.customMenuOptions.getClearIgnoredSyntaxChecks = function() {
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ $.extend(editorOptions, aceOptions);
|
|
|
+
|
|
|
+ editorOptions['enableBasicAutocompletion'] = apiHelper.getFromTotalStorage(
|
|
|
+ 'hue.ace',
|
|
|
+ 'enableBasicAutocompletion',
|
|
|
+ true
|
|
|
+ );
|
|
|
+ if (editorOptions['enableBasicAutocompletion']) {
|
|
|
+ editorOptions['enableLiveAutocompletion'] = apiHelper.getFromTotalStorage(
|
|
|
+ 'hue.ace',
|
|
|
+ 'enableLiveAutocompletion',
|
|
|
+ true
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.setOptions(editorOptions);
|
|
|
+
|
|
|
+ const AceAutocomplete = ace.require('ace/autocomplete').Autocomplete;
|
|
|
+
|
|
|
+ if (!editor.completer) {
|
|
|
+ editor.completer = new AceAutocomplete();
|
|
|
+ }
|
|
|
+ editor.completer.exactMatch = !snippet.isSqlDialect();
|
|
|
+
|
|
|
+ const initAutocompleters = function() {
|
|
|
+ if (editor.completers) {
|
|
|
+ editor.completers.length = 0;
|
|
|
+ if (snippet.type() === 'hive' || snippet.type() === 'impala') {
|
|
|
+ if (options.useNewAutocompleter) {
|
|
|
+ editor.useHueAutocompleter = true;
|
|
|
+ } else {
|
|
|
+ editor.completers.push(snippet.autocompleter);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ editor.completers.push(langTools.snippetCompleter);
|
|
|
+ editor.completers.push(langTools.textCompleter);
|
|
|
+ editor.completers.push(langTools.keyWordCompleter);
|
|
|
+ editor.completers.push(snippet.autocompleter);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const langTools = ace.require('ace/ext/language_tools');
|
|
|
+ langTools.textCompleter.setSqlMode(snippet.isSqlDialect());
|
|
|
+
|
|
|
+ initAutocompleters();
|
|
|
+
|
|
|
+ const UNICODES_TO_REMOVE = /[\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000\uFEFF]/gi; //taken from https://www.cs.tut.fi/~jkorpela/chars/spaces.html
|
|
|
+
|
|
|
+ const removeUnicodes = function(value) {
|
|
|
+ return value.replace(UNICODES_TO_REMOVE, ' ');
|
|
|
+ };
|
|
|
+
|
|
|
+ let placeHolderElement = null;
|
|
|
+ let placeHolderVisible = false;
|
|
|
+ const placeHolderText = snippet.getPlaceHolder();
|
|
|
+ if (placeHolderText) {
|
|
|
+ placeHolderElement = $('<div>')
|
|
|
+ .text(placeHolderText)
|
|
|
+ .css('margin-left', '6px')
|
|
|
+ .addClass('ace_invisible ace_emptyMessage');
|
|
|
+ if (editor.getValue().length === 0) {
|
|
|
+ placeHolderElement.appendTo(editor.renderer.scroller);
|
|
|
+ placeHolderVisible = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const pasteListener = editor.on('paste', e => {
|
|
|
+ e.text = removeUnicodes(e.text);
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('paste', pasteListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ const inputListener = editor.on('input', () => {
|
|
|
+ if (editor.getValue().length === 0) {
|
|
|
+ if (!placeHolderVisible && placeHolderElement) {
|
|
|
+ placeHolderElement.appendTo(editor.renderer.scroller);
|
|
|
+ placeHolderVisible = true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ placeHolderElement.remove();
|
|
|
+ placeHolderVisible = false;
|
|
|
+ }
|
|
|
+ if (options.updateOnInput) {
|
|
|
+ snippet.statement_raw(removeUnicodes(editor.getValue()));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('input', inputListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (snippet.aceCursorPosition()) {
|
|
|
+ editor.moveCursorToPosition(snippet.aceCursorPosition());
|
|
|
+ window.setTimeout(() => {
|
|
|
+ editor.centerSelection();
|
|
|
+ }, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ const focusListener = editor.on('focus', () => {
|
|
|
+ initAutocompleters();
|
|
|
+ snippet.inFocus(true);
|
|
|
+ $('.ace-editor').data('last-active-editor', false);
|
|
|
+ $el.data('last-active-editor', true);
|
|
|
+ if (editor.session.$backMarkers) {
|
|
|
+ for (const marker in editor.session.$backMarkers) {
|
|
|
+ if (editor.session.$backMarkers[marker].clazz === 'highlighted') {
|
|
|
+ editor.session.removeMarker(editor.session.$backMarkers[marker].id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('focus', focusListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ const changeSelectionListener = editor.selection.on('changeSelection', () => {
|
|
|
+ snippet.selectedStatement(editor.getSelectedText());
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.selection.off('changeSelection', changeSelectionListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ const blurListener = editor.on('blur', () => {
|
|
|
+ snippet.inFocus(false);
|
|
|
+ snippet.statement_raw(removeUnicodes(editor.getValue()));
|
|
|
+ if (options.onBlur) {
|
|
|
+ options.onBlur($el, removeUnicodes(editor.getValue()));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('blur', blurListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.previousSize = 0;
|
|
|
+
|
|
|
+ // TODO: Get rid of this
|
|
|
+ const idInterval = window.setInterval(() => {
|
|
|
+ editor.session.getMode().$id = snippet.getAceMode(); // forces the id again because of Ace command internals
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ window.clearInterval(idInterval);
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.middleClick = false;
|
|
|
+ const mousedownListener = editor.on('mousedown', e => {
|
|
|
+ if (e.domEvent.which === 2) {
|
|
|
+ // middle click
|
|
|
+ editor.middleClick = true;
|
|
|
+ const tempText = editor.getSelectedText();
|
|
|
+ if (e.$pos) {
|
|
|
+ editor.session.insert(e.$pos, tempText);
|
|
|
+ }
|
|
|
+ window.setTimeout(() => {
|
|
|
+ editor.middleClick = false;
|
|
|
+ if (e.$pos) {
|
|
|
+ editor.moveCursorTo(e.$pos.row, e.$pos.column + tempText.length);
|
|
|
+ }
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('mousedown', mousedownListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ const aceReplaceSub = huePubSub.subscribe('ace.replace', data => {
|
|
|
+ const Range = ace.require('ace/range').Range;
|
|
|
+ const range = new Range(
|
|
|
+ data.location.first_line - 1,
|
|
|
+ data.location.first_column - 1,
|
|
|
+ data.location.last_line - 1,
|
|
|
+ data.location.last_column - 1
|
|
|
+ );
|
|
|
+ editor
|
|
|
+ .getSession()
|
|
|
+ .getDocument()
|
|
|
+ .replace(range, data.text);
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ aceReplaceSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const clickListener = editor.on('click', () => {
|
|
|
+ editor.clearErrorsAndWarnings();
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('click', clickListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ const changeListener = editor.on('change', () => {
|
|
|
+ snippet.statement_raw(removeUnicodes(editor.getValue()));
|
|
|
+ editor.session.getMode().$id = snippet.getAceMode();
|
|
|
+ const currentSize = editor.session.getLength();
|
|
|
+ if (
|
|
|
+ currentSize !== editor.previousSize &&
|
|
|
+ currentSize >= editorOptions.minLines &&
|
|
|
+ currentSize <= editorOptions.maxLines
|
|
|
+ ) {
|
|
|
+ editor.previousSize = editor.session.getLength();
|
|
|
+ $(document).trigger('editorSizeChanged');
|
|
|
+ }
|
|
|
+ // automagically change snippet type
|
|
|
+ const firstLine = editor.session.getLine(0);
|
|
|
+ if (firstLine.indexOf('%') === 0 && firstLine.charAt(firstLine.length - 1) === ' ') {
|
|
|
+ const availableSnippets = snippet.availableSnippets;
|
|
|
+ let removeFirstLine = false;
|
|
|
+ for (let i = 0; i < availableSnippets.length; i++) {
|
|
|
+ if ($.trim(firstLine.substr(1)) === availableSnippets[i].type()) {
|
|
|
+ snippet.type(availableSnippets[i].type());
|
|
|
+ removeFirstLine = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (removeFirstLine) {
|
|
|
+ editor.session.remove(new AceRange(0, 0, 0, 200));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.off('change', changeListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'execute',
|
|
|
+ bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter|Ctrl-Enter' },
|
|
|
+ exec: function() {
|
|
|
+ snippet.statement_raw(removeUnicodes(editor.getValue()));
|
|
|
+ snippet.execute();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'switchTheme',
|
|
|
+ bindKey: { win: 'Ctrl-Alt-t', mac: 'Command-Alt-t' },
|
|
|
+ exec: function() {
|
|
|
+ darkThemeEnabled = !darkThemeEnabled;
|
|
|
+ apiHelper.setInTotalStorage('ace', 'dark.theme.enabled', darkThemeEnabled);
|
|
|
+ editor.setTheme(darkThemeEnabled ? 'ace/theme/hue_dark' : 'ace/theme/hue');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'new',
|
|
|
+ bindKey: { win: 'Ctrl-e', mac: 'Command-e' },
|
|
|
+ exec: function() {
|
|
|
+ huePubSub.publish('editor.create.new');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'save',
|
|
|
+ bindKey: { win: 'Ctrl-s', mac: 'Command-s|Ctrl-s' },
|
|
|
+ exec: function() {
|
|
|
+ huePubSub.publish('editor.save');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'esc',
|
|
|
+ bindKey: { win: 'Ctrl-Shift-p', mac: 'Ctrl-Shift-p|Command-Shift-p' },
|
|
|
+ exec: function() {
|
|
|
+ huePubSub.publish('editor.presentation.toggle');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.bindKey('Ctrl-P', 'golineup');
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'format',
|
|
|
+ bindKey: {
|
|
|
+ win: 'Ctrl-i|Ctrl-Shift-f|Ctrl-Alt-l',
|
|
|
+ mac: 'Command-i|Ctrl-i|Ctrl-Shift-f|Command-Shift-f|Ctrl-Shift-l|Cmd-Shift-l'
|
|
|
+ },
|
|
|
+ exec: function() {
|
|
|
+ if (
|
|
|
+ [
|
|
|
+ 'ace/mode/hive',
|
|
|
+ 'ace/mode/impala',
|
|
|
+ 'ace/mode/sql',
|
|
|
+ 'ace/mode/mysql',
|
|
|
+ 'ace/mode/pgsql',
|
|
|
+ 'ace/mode/sqlite',
|
|
|
+ 'ace/mode/oracle'
|
|
|
+ ].indexOf(snippet.getAceMode()) > -1
|
|
|
+ ) {
|
|
|
+ $.post(
|
|
|
+ '/notebook/api/format',
|
|
|
+ {
|
|
|
+ statements:
|
|
|
+ editor.getSelectedText() !== '' ? editor.getSelectedText() : editor.getValue()
|
|
|
+ },
|
|
|
+ data => {
|
|
|
+ if (data.status === 0) {
|
|
|
+ if (editor.getSelectedText() !== '') {
|
|
|
+ editor.session.replace(
|
|
|
+ editor.session.selection.getRange(),
|
|
|
+ data.formatted_statements
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ editor.setValue(data.formatted_statements);
|
|
|
+ snippet.statement_raw(removeUnicodes(editor.getValue()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.commands.addCommand({
|
|
|
+ name: 'gotolinealternative',
|
|
|
+ bindKey: { win: 'Ctrl-j', mac: 'Command-j|Ctrl-j' },
|
|
|
+ exec: editor.commands.commands['gotoline'].exec
|
|
|
+ });
|
|
|
+
|
|
|
+ const isNewStatement = function() {
|
|
|
+ return /^\s*$/.test(editor.getValue()) || /^.*;\s*$/.test(editor.getTextBeforeCursor());
|
|
|
+ };
|
|
|
+
|
|
|
+ const insertSqlAtCursor = function(text, cursorEndAdjust, menu) {
|
|
|
+ const before = editor.getTextBeforeCursor();
|
|
|
+ if (/\S+$/.test(before)) {
|
|
|
+ text = ' ' + text;
|
|
|
+ }
|
|
|
+ if (menu) {
|
|
|
+ menu.hide();
|
|
|
+ }
|
|
|
+ editor.session.insert(editor.getCursorPosition(), text);
|
|
|
+ if (cursorEndAdjust !== 0) {
|
|
|
+ const cursor = editor.getCursorPosition();
|
|
|
+ editor.moveCursorToPosition({ row: cursor.row, column: cursor.column + cursorEndAdjust });
|
|
|
+ }
|
|
|
+ editor.clearSelection();
|
|
|
+ editor.focus();
|
|
|
+ };
|
|
|
+
|
|
|
+ const insertTableAtCursorSub = huePubSub.subscribe('editor.insert.table.at.cursor', details => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ const qualifiedName =
|
|
|
+ snippet.database() === details.database
|
|
|
+ ? details.name
|
|
|
+ : details.database + '.' + details.name;
|
|
|
+ if (isNewStatement()) {
|
|
|
+ insertSqlAtCursor('SELECT * FROM ' + qualifiedName + ' LIMIT 100;', -1);
|
|
|
+ } else {
|
|
|
+ insertSqlAtCursor(qualifiedName + ' ', 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const insertColumnAtCursorSub = huePubSub.subscribe(
|
|
|
+ 'editor.insert.column.at.cursor',
|
|
|
+ details => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ if (isNewStatement()) {
|
|
|
+ const qualifiedFromName =
|
|
|
+ snippet.database() === details.database
|
|
|
+ ? details.table
|
|
|
+ : details.database + '.' + details.table;
|
|
|
+ insertSqlAtCursor(
|
|
|
+ 'SELECT ' + details.name + ' FROM ' + qualifiedFromName + ' LIMIT 100;',
|
|
|
+ -1
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ insertSqlAtCursor(details.name + ' ', 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const insertAtCursorSub = huePubSub.subscribe('editor.insert.at.cursor', text => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ insertSqlAtCursor(text + ' ', 0);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ insertTableAtCursorSub.remove();
|
|
|
+ insertColumnAtCursorSub.remove();
|
|
|
+ insertAtCursorSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const dblClickHdfsItemSub = huePubSub.subscribe('assist.dblClickHdfsItem', assistHdfsEntry => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ editor.session.insert(editor.getCursorPosition(), "'" + assistHdfsEntry.path + "'");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ dblClickHdfsItemSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const dblClickAdlsItemSub = huePubSub.subscribe('assist.dblClickAdlsItem', assistHdfsEntry => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ editor.session.insert(editor.getCursorPosition(), 'adl:/' + assistHdfsEntry.path + "'");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ dblClickAdlsItemSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const dblClickGitItemSub = huePubSub.subscribe('assist.dblClickGitItem', assistGitEntry => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ editor.session.setValue(assistGitEntry.fileContent());
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ dblClickGitItemSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const dblClickS3ItemSub = huePubSub.subscribe('assist.dblClickS3Item', assistS3Entry => {
|
|
|
+ if ($el.data('last-active-editor')) {
|
|
|
+ editor.session.insert(editor.getCursorPosition(), "'S3A://" + assistS3Entry.path + "'");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ dblClickS3ItemSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const sampleErrorInsertSub = huePubSub.subscribe('sample.error.insert.click', popoverEntry => {
|
|
|
+ const table = popoverEntry.identifierChain[popoverEntry.identifierChain.length - 1]['name'];
|
|
|
+ const text = 'SELECT * FROM ' + table + ' LIMIT 100;';
|
|
|
+ insertSqlAtCursor(text, -1);
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ sampleErrorInsertSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const $tableDropMenu = $el.next('.table-drop-menu');
|
|
|
+ const $identifierDropMenu = $tableDropMenu.find('.editor-drop-identifier');
|
|
|
+
|
|
|
+ const hideDropMenu = function() {
|
|
|
+ $tableDropMenu.css('opacity', 0);
|
|
|
+ window.setTimeout(() => {
|
|
|
+ $tableDropMenu.hide();
|
|
|
+ }, 300);
|
|
|
+ };
|
|
|
+
|
|
|
+ const documentClickListener = function(event) {
|
|
|
+ if ($tableDropMenu.find($(event.target)).length === 0) {
|
|
|
+ hideDropMenu();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ $(document).on('click', documentClickListener);
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ $(document).off('click', documentClickListener);
|
|
|
+ });
|
|
|
+
|
|
|
+ let lastMeta = {};
|
|
|
+ const draggableTextSub = huePubSub.subscribe('draggable.text.meta', meta => {
|
|
|
+ lastMeta = meta;
|
|
|
+ if (meta.isView) {
|
|
|
+ $tableDropMenu.find('.editor-drop-update').hide();
|
|
|
+ $tableDropMenu.find('.editor-drop-insert').hide();
|
|
|
+ $tableDropMenu.find('.editor-drop-drop').hide();
|
|
|
+ $tableDropMenu.find('.editor-drop-view').show();
|
|
|
+ } else {
|
|
|
+ $tableDropMenu.find('.editor-drop-update').show();
|
|
|
+ $tableDropMenu.find('.editor-drop-insert').show();
|
|
|
+ $tableDropMenu.find('.editor-drop-drop').show();
|
|
|
+ $tableDropMenu.find('.editor-drop-view').hide();
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ typeof meta !== 'undefined' &&
|
|
|
+ typeof meta.database !== 'undefined' &&
|
|
|
+ typeof meta.table !== 'undefined'
|
|
|
+ ) {
|
|
|
+ $identifierDropMenu.text(meta.database + '.' + meta.table);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ draggableTextSub.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ const menu = ko.bindingHandlers.contextMenu.initContextMenu(
|
|
|
+ $tableDropMenu,
|
|
|
+ $('.content-panel')
|
|
|
+ );
|
|
|
+
|
|
|
+ $tableDropMenu.find('.editor-drop-value').click(() => {
|
|
|
+ insertSqlAtCursor(
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.database) +
|
|
|
+ '.' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.table) +
|
|
|
+ ' ',
|
|
|
+ 0,
|
|
|
+ menu
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ $tableDropMenu.find('.editor-drop-select').click(() => {
|
|
|
+ insertSqlAtCursor(
|
|
|
+ 'SELECT * FROM ' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.database) +
|
|
|
+ '.' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.table) +
|
|
|
+ ' LIMIT 100;',
|
|
|
+ -1,
|
|
|
+ menu
|
|
|
+ );
|
|
|
+ $tableDropMenu.hide();
|
|
|
+ });
|
|
|
+
|
|
|
+ $tableDropMenu.find('.editor-drop-insert').click(() => {
|
|
|
+ insertSqlAtCursor(
|
|
|
+ 'INSERT INTO ' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.database) +
|
|
|
+ '.' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.table) +
|
|
|
+ ' VALUES ();',
|
|
|
+ -2,
|
|
|
+ menu
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ $tableDropMenu.find('.editor-drop-update').click(() => {
|
|
|
+ insertSqlAtCursor(
|
|
|
+ 'UPDATE ' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.database) +
|
|
|
+ '.' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.table) +
|
|
|
+ ' SET ',
|
|
|
+ 0,
|
|
|
+ menu
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ $tableDropMenu.find('.editor-drop-view').click(() => {
|
|
|
+ insertSqlAtCursor(
|
|
|
+ 'DROP VIEW ' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.database) +
|
|
|
+ '.' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.table) +
|
|
|
+ ';',
|
|
|
+ -1,
|
|
|
+ menu
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ $tableDropMenu.find('.editor-drop-drop').click(() => {
|
|
|
+ insertSqlAtCursor(
|
|
|
+ 'DROP TABLE ' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.database) +
|
|
|
+ '.' +
|
|
|
+ sqlUtils.backTickIfNeeded(lastMeta.type, lastMeta.table) +
|
|
|
+ ';',
|
|
|
+ -1,
|
|
|
+ menu
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ $el.droppable({
|
|
|
+ accept: '.draggableText',
|
|
|
+ drop: function(e, ui) {
|
|
|
+ const position = editor.renderer.screenToTextCoordinates(e.clientX, e.clientY);
|
|
|
+ let text = ui.helper.text();
|
|
|
+ if (lastMeta.type === 's3' || lastMeta.type === 'hdfs' || lastMeta.type === 'adls') {
|
|
|
+ text = "'" + lastMeta.definition.path + "'";
|
|
|
+ }
|
|
|
+ editor.moveCursorToPosition(position);
|
|
|
+ const before = editor.getTextBeforeCursor();
|
|
|
+ if (lastMeta.database && lastMeta.table && !lastMeta.column && /.*;|^\s*$/.test(before)) {
|
|
|
+ menu.show(e);
|
|
|
+ } else {
|
|
|
+ if (/\S+$/.test(before) && before.charAt(before.length - 1) !== '.') {
|
|
|
+ text = ' ' + text;
|
|
|
+ }
|
|
|
+ const after = editor.getTextAfterCursor();
|
|
|
+ if (after.length > 0 && after.charAt(0) !== ' ' && text.charAt(text.length - 1) !== ' ') {
|
|
|
+ text += ' ';
|
|
|
+ }
|
|
|
+ editor.session.insert(position, text);
|
|
|
+ position.column += text.length;
|
|
|
+ editor.clearSelection();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ let autocompleteTemporarilyDisabled = false;
|
|
|
+ let autocompleteThrottle = -1;
|
|
|
+ const afterExecListener = editor.commands.on('afterExec', e => {
|
|
|
+ if (editor.getOption('enableLiveAutocompletion') && e.command.name === 'insertstring') {
|
|
|
+ if (/\S+\(\)$/.test(e.args)) {
|
|
|
+ editor.moveCursorTo(
|
|
|
+ editor.getCursorPosition().row,
|
|
|
+ editor.getCursorPosition().column - 1
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ window.clearTimeout(autocompleteThrottle);
|
|
|
+ autocompleteThrottle = window.setTimeout(() => {
|
|
|
+ const textBeforeCursor = editor.getTextBeforeCursor();
|
|
|
+ let questionMarkMatch;
|
|
|
+ if ($('.hue-ace-autocompleter').length > 0) {
|
|
|
+ questionMarkMatch = textBeforeCursor.match(/select\s+(\? from \S+[^.]\s$)/i);
|
|
|
+ } else {
|
|
|
+ questionMarkMatch = textBeforeCursor.match(/select\s+(\? from \S+[^.]$)/i);
|
|
|
+ }
|
|
|
+ if (questionMarkMatch && $('.ace_autocomplete:visible').length === 0) {
|
|
|
+ editor.moveCursorTo(
|
|
|
+ editor.getCursorPosition().row,
|
|
|
+ editor.getCursorPosition().column - (questionMarkMatch[1].length - 1)
|
|
|
+ );
|
|
|
+ editor.removeTextBeforeCursor(1);
|
|
|
+ huePubSub.publish('editor.refresh.statement.locations', snippet);
|
|
|
+ window.setTimeout(() => {
|
|
|
+ editor.execCommand('startAutocomplete');
|
|
|
+ }, 1);
|
|
|
+ } else if (/\.$/.test(textBeforeCursor)) {
|
|
|
+ huePubSub.publish('editor.refresh.statement.locations', snippet);
|
|
|
+ window.setTimeout(() => {
|
|
|
+ editor.execCommand('startAutocomplete');
|
|
|
+ }, 1);
|
|
|
+ }
|
|
|
+ }, 400);
|
|
|
+ }
|
|
|
+ editor.session.getMode().$id = snippet.getAceMode(); // forces the id again because of Ace command internals
|
|
|
+ // if it's pig and before it's LOAD ' we disable the autocomplete and show a filechooser btn
|
|
|
+ if (editor.session.getMode().$id === 'ace/mode/pig' && e.args) {
|
|
|
+ const textBefore = editor.getTextBeforeCursor();
|
|
|
+ if (
|
|
|
+ (e.args === "'" &&
|
|
|
+ textBefore.toUpperCase().indexOf('LOAD ') > -1 &&
|
|
|
+ textBefore.toUpperCase().indexOf('LOAD ') === textBefore.toUpperCase().length - 5) ||
|
|
|
+ (textBefore.toUpperCase().indexOf("LOAD '") > -1 &&
|
|
|
+ textBefore.toUpperCase().indexOf("LOAD '") === textBefore.toUpperCase().length - 6)
|
|
|
+ ) {
|
|
|
+ if (editor.getOption('enableBasicAutocompletion')) {
|
|
|
+ editor.disableAutocomplete();
|
|
|
+ autocompleteTemporarilyDisabled = true;
|
|
|
+ }
|
|
|
+ const btn = editor.showFileButton();
|
|
|
+ btn.on('click', ie => {
|
|
|
+ ie.preventDefault();
|
|
|
+ // TODO: Turn the ace file chooser into a component and remove css class references
|
|
|
+ const $aceFileChooseContent = $('.ace-filechooser-content');
|
|
|
+ if (!$aceFileChooseContent.data('jHueFileChooser')) {
|
|
|
+ if ($aceFileChooseContent.data('spinner') == null) {
|
|
|
+ $aceFileChooseContent.data('spinner', $aceFileChooseContent.html());
|
|
|
+ } else {
|
|
|
+ $aceFileChooseContent.html($aceFileChooseContent.data('spinner'));
|
|
|
+ }
|
|
|
+ $aceFileChooseContent.jHueFileChooser({
|
|
|
+ onFileChoose: function(filePath) {
|
|
|
+ editor.session.insert(editor.getCursorPosition(), filePath + "'");
|
|
|
+ editor.hideFileButton();
|
|
|
+ if (autocompleteTemporarilyDisabled) {
|
|
|
+ editor.enableAutocomplete();
|
|
|
+ autocompleteTemporarilyDisabled = false;
|
|
|
+ }
|
|
|
+ $('.ace-filechooser').hide();
|
|
|
+ },
|
|
|
+ selectFolder: false,
|
|
|
+ createFolder: false
|
|
|
+ });
|
|
|
+ }
|
|
|
+ $('.ace-filechooser')
|
|
|
+ .css({
|
|
|
+ top: $(ie.currentTarget).position().top,
|
|
|
+ left: $(ie.currentTarget).position().left
|
|
|
+ })
|
|
|
+ .show();
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ editor.hideFileButton();
|
|
|
+ if (autocompleteTemporarilyDisabled) {
|
|
|
+ editor.enableAutocomplete();
|
|
|
+ autocompleteTemporarilyDisabled = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ e.args !== "'" &&
|
|
|
+ textBefore.toUpperCase().indexOf("LOAD '") > -1 &&
|
|
|
+ textBefore.toUpperCase().indexOf("LOAD '") === textBefore.toUpperCase().length - 6
|
|
|
+ ) {
|
|
|
+ editor.hideFileButton();
|
|
|
+ if (autocompleteTemporarilyDisabled) {
|
|
|
+ editor.enableAutocomplete();
|
|
|
+ autocompleteTemporarilyDisabled = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ disposeFunctions.push(() => {
|
|
|
+ editor.commands.off('afterExec', afterExecListener);
|
|
|
+ });
|
|
|
+ editor.$blockScrolling = Infinity;
|
|
|
+ snippet.ace(editor);
|
|
|
+ },
|
|
|
+
|
|
|
+ update: function(element, valueAccessor) {
|
|
|
+ const options = ko.unwrap(valueAccessor());
|
|
|
+ const snippet = options.snippet;
|
|
|
+ const AceRange = ace.require('ace/range').Range;
|
|
|
+ if (snippet.ace()) {
|
|
|
+ const editor = snippet.ace();
|
|
|
+ if (typeof options.readOnly !== 'undefined') {
|
|
|
+ editor.setReadOnly(options.readOnly);
|
|
|
+ }
|
|
|
+ const range = options.highlightedRange ? options.highlightedRange() : null;
|
|
|
+ editor.session.setMode(snippet.getAceMode());
|
|
|
+ if (range && JSON.stringify(range.start) !== JSON.stringify(range.end)) {
|
|
|
+ let conflictingWithErrorMarkers = false;
|
|
|
+ if (editor.session.$backMarkers) {
|
|
|
+ for (const marker in editor.session.$backMarkers) {
|
|
|
+ if (editor.session.$backMarkers[marker].clazz === 'ace_error-line') {
|
|
|
+ const errorRange = editor.session.$backMarkers[marker].range;
|
|
|
+ if (range.start.row <= errorRange.end.row && range.end.row >= errorRange.start.row) {
|
|
|
+ conflictingWithErrorMarkers = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (editor.session.$backMarkers[marker].clazz === 'highlighted') {
|
|
|
+ editor.session.removeMarker(editor.session.$backMarkers[marker].id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!conflictingWithErrorMarkers) {
|
|
|
+ const lineOffset = snippet.lastAceSelectionRowOffset();
|
|
|
+ window.setTimeout(() => {
|
|
|
+ editor.session.addMarker(
|
|
|
+ new AceRange(
|
|
|
+ range.start.row + lineOffset,
|
|
|
+ range.start.column,
|
|
|
+ range.end.row + lineOffset,
|
|
|
+ range.end.column
|
|
|
+ ),
|
|
|
+ 'highlighted',
|
|
|
+ 'line'
|
|
|
+ );
|
|
|
+ ace
|
|
|
+ .require('ace/lib/dom')
|
|
|
+ .importCssString(
|
|
|
+ '.highlighted {\
|
|
|
+ background-color: #E3F7FF;\
|
|
|
+ position: absolute;\
|
|
|
+ }'
|
|
|
+ );
|
|
|
+ editor.scrollToLine(range.start.row + lineOffset, true, true, () => {});
|
|
|
+ }, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ editor._emit('change');
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|