gethue
Version:
Hue is an Open source SQL Query Editor for Databases/Warehouses
180 lines (169 loc) • 6.02 kB
JavaScript
// 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 AutocompleteResults from '../sql/autocompleteResults';
import hueDebug from '../utils/hueDebug';
import huePubSub from '../utils/huePubSub';
import sqlParserRepository from '../parse/sql/sqlParserRepository';
class SqlAutocompleter {
/**
* @param {Object} options
* @param {Snippet} options.snippet
* @param {string} [options.fixedPrefix] - Optional prefix to always use on parse
* @param {string} [options.fixedPostfix] - Optional postfix to always use on parse
* @constructor
*/
constructor(options) {
this.snippet = options.snippet;
this.editor = options.editor;
this.fixedPrefix =
options.fixedPrefix ||
function () {
return '';
};
this.fixedPostfix =
options.fixedPostfix ||
function () {
return '';
};
this.suggestions = new AutocompleteResults(options);
}
async parseActiveStatement() {
return new Promise((resolve, reject) => {
if (this.snippet.positionStatement() && this.snippet.positionStatement().location) {
const activeStatementLocation = this.snippet.positionStatement().location;
const cursorPosition = this.editor().getCursorPosition();
if (
(activeStatementLocation.first_line - 1 < cursorPosition.row ||
(activeStatementLocation.first_line - 1 === cursorPosition.row &&
activeStatementLocation.first_column <= cursorPosition.column)) &&
(activeStatementLocation.last_line - 1 > cursorPosition.row ||
(activeStatementLocation.last_line - 1 === cursorPosition.row &&
activeStatementLocation.last_column >= cursorPosition.column))
) {
const beforeCursor =
this.fixedPrefix() +
this.editor().session.getTextRange({
start: {
row: activeStatementLocation.first_line - 1,
column: activeStatementLocation.first_column
},
end: cursorPosition
});
const afterCursor =
this.editor().session.getTextRange({
start: cursorPosition,
end: {
row: activeStatementLocation.last_line - 1,
column: activeStatementLocation.last_column
}
}) + this.fixedPostfix();
sqlParserRepository
.getAutocompleteParser(this.snippet.dialect())
.then(autocompleteParser => {
resolve(autocompleteParser.parseSql(beforeCursor, afterCursor));
})
.catch(err => {
console.warn(err);
reject(err);
});
} else {
resolve();
}
} else {
resolve();
}
});
}
async parseAll() {
return new Promise((resolve, reject) => {
sqlParserRepository
.getAutocompleteParser(this.snippet.dialect())
.then(autocompleteParser => {
resolve(
autocompleteParser.parseSql(
this.editor().getTextBeforeCursor(),
this.editor().getTextAfterCursor()
)
);
})
.catch(reject);
});
}
async autocomplete() {
let parseResult;
try {
huePubSub.publish(
'get.active.editor.locations',
locations => {
// This could happen in case the user is editing at the borders of the statement and the locations haven't
// been updated yet, in that case we have to force a location update before parsing
if (
this.snippet.ace &&
this.snippet.ace() &&
locations &&
this.snippet.ace().lastChangeTime !== locations.editorChangeTime
) {
huePubSub.publish('editor.refresh.statement.locations', this.snippet.id());
}
},
this.snippet
);
parseResult = await this.parseActiveStatement();
} catch (e) {
if (typeof console.warn !== 'undefined') {
console.warn(e);
}
}
// In the unlikely case the statement parser fails we fall back to parsing all of it
if (!parseResult) {
try {
parseResult = await this.parseAll();
} catch (e) {
if (typeof console.warn !== 'undefined') {
console.warn(e);
}
}
}
if (!parseResult) {
// This prevents Ace from inserting garbled text in case of exception
huePubSub.publish('hue.ace.autocompleter.done');
} else {
if (typeof hueDebug !== 'undefined' && hueDebug.showParseResult) {
// eslint-disable-next-line no-restricted-syntax
console.log(parseResult);
}
try {
if (this.lastContextRequest) {
this.lastContextRequest.dispose();
}
this.lastContextRequest = this.snippet
.whenContextSet()
.done(() => {
this.suggestions.update(parseResult);
})
.fail(() => {
huePubSub.publish('hue.ace.autocompleter.done');
});
} catch (e) {
if (typeof console.warn !== 'undefined') {
console.warn(e);
}
huePubSub.publish('hue.ace.autocompleter.done');
}
}
}
}
export default SqlAutocompleter;