UNPKG

gethue

Version:

Hue is an Open source SQL Query Editor for Databases/Warehouses

848 lines (756 loc) 29.5 kB
// 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 * as ko from 'knockout'; import ace from '../../../ext/aceHelper'; import { format } from '@gethue/sql-formatter'; import AceLocationHandler, { REFRESH_STATEMENT_LOCATIONS_EVENT } from '../../../ko/bindings/ace/aceLocationHandler'; import AceGutterHandler from '../../../ko/bindings/ace/aceGutterHandler'; import { registerBinding } from '../../../ko/bindings/bindingUtils'; import huePubSub from '../../../utils/huePubSub'; import { getFromLocalStorage, setInLocalStorage } from '../../../utils/storageUtils'; export const NAME = 'aceEditor'; export const INSERT_AT_CURSOR_EVENT = 'editor.insert.at.cursor'; registerBinding(NAME, { 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(snippet.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 } }); const aceGutterHandler = new AceGutterHandler({ editor: editor, editorId: $el.attr('id'), executor: snippet.executor }); disposeFunctions.push(() => { aceLocationHandler.dispose(); aceGutterHandler.dispose(); }); editor.session.setMode(snippet.getAceMode()); editor.setOptions({ fontSize: getFromLocalStorage( '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 = getFromLocalStorage('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; setInLocalStorage('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); setInLocalStorage('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); setInLocalStorage('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); setInLocalStorage('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 = getFromLocalStorage('hue.ace.errorHighlightingEnabled', true); if (errorHighlightingEnabled) { aceLocationHandler.attachSqlSyntaxWorker(); } editor.customMenuOptions.setErrorHighlighting = function (enabled) { errorHighlightingEnabled = enabled; setInLocalStorage('hue.ace.errorHighlightingEnabled', enabled); if (enabled) { aceLocationHandler.attachSqlSyntaxWorker(); } else { aceLocationHandler.detachSqlSyntaxWorker(); } }; editor.customMenuOptions.getErrorHighlighting = function () { return errorHighlightingEnabled; }; editor.customMenuOptions.setClearIgnoredSyntaxChecks = function () { setInLocalStorage('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'] = getFromLocalStorage( 'hue.ace.enableBasicAutocompletion', true ); if (editorOptions['enableBasicAutocompletion']) { editorOptions['enableLiveAutocompletion'] = getFromLocalStorage( 'hue.ace.enableLiveAutocompletion', true ); } editorOptions['tabSize'] = 2; editorOptions['useSoftTabs'] = 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.isSqlDialect()) { editor.useHueAutocompleter = true; } 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 // TODO: Remove completely, check if used in code, '% dialect' 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; setInLocalStorage('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 () { const formatted_statements = format( editor.getSelectedText() !== '' ? editor.getSelectedText() : editor.getValue(), { uppercase: true, linesBetweenQueries: 2, indentQuerySeparator: true } ); if (editor.getSelectedText() !== '') { editor.session.replace(editor.session.selection.getRange(), formatted_statements); } else { editor.setValue(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(INSERT_AT_CURSOR_EVENT, details => { if ( (details.targetEditor && details.targetEditor === editor) || $el.data('last-active-editor') ) { insertSqlAtCursor(details.text + ' ', details.cursorEndAdjust || 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 dblClickAbfsItemSub = huePubSub.subscribe('assist.dblClickAbfsItem', assistHdfsEntry => { if ($el.data('last-active-editor')) { editor.session.insert(editor.getCursorPosition(), 'abfs://' + assistHdfsEntry.path + "'"); } }); disposeFunctions.push(() => { dblClickAbfsItemSub.remove(); }); const dblClickOfsItemSub = huePubSub.subscribe('assist.dblClickOfsItem', assistOfsEntry => { if ($el.data('last-active-editor')) { editor.session.insert(editor.getCursorPosition(), 'ofs://' + assistOfsEntry.path + "'"); } }); disposeFunctions.push(() => { dblClickOfsItemSub.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(); }); 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(); const questionMarkMatch = textBeforeCursor.match(/select\s+(\? from \S+[^.]\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(REFRESH_STATEMENT_LOCATIONS_EVENT, snippet.id()); window.setTimeout(() => { editor.execCommand('startAutocomplete'); }, 1); } else if (/\.$/.test(textBeforeCursor)) { huePubSub.publish(REFRESH_STATEMENT_LOCATIONS_EVENT, snippet.id()); 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) {} } } });