| 1 |
// CodeMirror, copyright (c) by Marijn Haverbeke and others |
| 2 |
// Distributed under an MIT license: http://codemirror.net/LICENSE |
| 3 |
|
| 4 |
(function(mod) { |
| 5 |
if (typeof exports == "object" && typeof module == "object") // CommonJS |
| 6 |
mod(require("../../lib/codemirror"), require("../../mode/sql/sql")); |
| 7 |
else if (typeof define == "function" && define.amd) // AMD |
| 8 |
define(["../../lib/codemirror", "../../mode/sql/sql"], mod); |
| 9 |
else // Plain browser env |
| 10 |
mod(CodeMirror); |
| 11 |
})(function(CodeMirror) { |
| 12 |
"use strict"; |
| 13 |
|
| 14 |
var tables; |
| 15 |
var defaultTable; |
| 16 |
var keywords; |
| 17 |
var identifierQuote; |
| 18 |
var CONS = { |
| 19 |
QUERY_DIV: ";", |
| 20 |
ALIAS_KEYWORD: "AS" |
| 21 |
}; |
| 22 |
var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos; |
| 23 |
|
| 24 |
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" } |
| 25 |
|
| 26 |
function getKeywords(editor) { |
| 27 |
var mode = editor.doc.modeOption; |
| 28 |
if (mode === "sql") mode = "text/x-sql"; |
| 29 |
return CodeMirror.resolveMode(mode).keywords; |
| 30 |
} |
| 31 |
|
| 32 |
function getIdentifierQuote(editor) { |
| 33 |
var mode = editor.doc.modeOption; |
| 34 |
if (mode === "sql") mode = "text/x-sql"; |
| 35 |
return CodeMirror.resolveMode(mode).identifierQuote || "`"; |
| 36 |
} |
| 37 |
|
| 38 |
function getText(item) { |
| 39 |
return typeof item == "string" ? item : item.text; |
| 40 |
} |
| 41 |
|
| 42 |
function wrapTable(name, value) { |
| 43 |
if (isArray(value)) value = {columns: value} |
| 44 |
if (!value.text) value.text = name |
| 45 |
return value |
| 46 |
} |
| 47 |
|
| 48 |
function parseTables(input) { |
| 49 |
var result = {} |
| 50 |
if (isArray(input)) { |
| 51 |
for (var i = input.length - 1; i >= 0; i--) { |
| 52 |
var item = input[i] |
| 53 |
result[getText(item).toUpperCase()] = wrapTable(getText(item), item) |
| 54 |
} |
| 55 |
} else if (input) { |
| 56 |
for (var name in input) |
| 57 |
result[name.toUpperCase()] = wrapTable(name, input[name]) |
| 58 |
} |
| 59 |
return result |
| 60 |
} |
| 61 |
|
| 62 |
function getTable(name) { |
| 63 |
return tables[name.toUpperCase()] |
| 64 |
} |
| 65 |
|
| 66 |
function shallowClone(object) { |
| 67 |
var result = {}; |
| 68 |
for (var key in object) if (object.hasOwnProperty(key)) |
| 69 |
result[key] = object[key]; |
| 70 |
return result; |
| 71 |
} |
| 72 |
|
| 73 |
function match(string, word) { |
| 74 |
var len = string.length; |
| 75 |
var sub = getText(word).substr(0, len); |
| 76 |
return string.toUpperCase() === sub.toUpperCase(); |
| 77 |
} |
| 78 |
|
| 79 |
function addMatches(result, search, wordlist, formatter) { |
| 80 |
if (isArray(wordlist)) { |
| 81 |
for (var i = 0; i < wordlist.length; i++) |
| 82 |
if (match(search, wordlist[i])) result.push(formatter(wordlist[i])) |
| 83 |
} else { |
| 84 |
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) { |
| 85 |
var val = wordlist[word] |
| 86 |
if (!val || val === true) |
| 87 |
val = word |
| 88 |
else |
| 89 |
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text |
| 90 |
if (match(search, val)) result.push(formatter(val)) |
| 91 |
} |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
function cleanName(name) { |
| 96 |
// Get rid name from identifierQuote and preceding dot(.) |
| 97 |
if (name.charAt(0) == ".") { |
| 98 |
name = name.substr(1); |
| 99 |
} |
| 100 |
// replace doublicated identifierQuotes with single identifierQuotes |
| 101 |
// and remove single identifierQuotes |
| 102 |
var nameParts = name.split(identifierQuote+identifierQuote); |
| 103 |
for (var i = 0; i < nameParts.length; i++) |
| 104 |
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), ""); |
| 105 |
return nameParts.join(identifierQuote); |
| 106 |
} |
| 107 |
|
| 108 |
function insertIdentifierQuotes(name) { |
| 109 |
var nameParts = getText(name).split("."); |
| 110 |
for (var i = 0; i < nameParts.length; i++) |
| 111 |
nameParts[i] = identifierQuote + |
| 112 |
// doublicate identifierQuotes |
| 113 |
nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) + |
| 114 |
identifierQuote; |
| 115 |
var escaped = nameParts.join("."); |
| 116 |
if (typeof name == "string") return escaped; |
| 117 |
name = shallowClone(name); |
| 118 |
name.text = escaped; |
| 119 |
return name; |
| 120 |
} |
| 121 |
|
| 122 |
function nameCompletion(cur, token, result, editor) { |
| 123 |
// Try to complete table, column names and return start position of completion |
| 124 |
var useIdentifierQuotes = false; |
| 125 |
var nameParts = []; |
| 126 |
var start = token.start; |
| 127 |
var cont = true; |
| 128 |
while (cont) { |
| 129 |
cont = (token.string.charAt(0) == "."); |
| 130 |
useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote); |
| 131 |
|
| 132 |
start = token.start; |
| 133 |
nameParts.unshift(cleanName(token.string)); |
| 134 |
|
| 135 |
token = editor.getTokenAt(Pos(cur.line, token.start)); |
| 136 |
if (token.string == ".") { |
| 137 |
cont = true; |
| 138 |
token = editor.getTokenAt(Pos(cur.line, token.start)); |
| 139 |
} |
| 140 |
} |
| 141 |
|
| 142 |
// Try to complete table names |
| 143 |
var string = nameParts.join("."); |
| 144 |
addMatches(result, string, tables, function(w) { |
| 145 |
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w; |
| 146 |
}); |
| 147 |
|
| 148 |
// Try to complete columns from defaultTable |
| 149 |
addMatches(result, string, defaultTable, function(w) { |
| 150 |
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w; |
| 151 |
}); |
| 152 |
|
| 153 |
// Try to complete columns |
| 154 |
string = nameParts.pop(); |
| 155 |
var table = nameParts.join("."); |
| 156 |
|
| 157 |
var alias = false; |
| 158 |
var aliasTable = table; |
| 159 |
// Check if table is available. If not, find table by Alias |
| 160 |
if (!getTable(table)) { |
| 161 |
var oldTable = table; |
| 162 |
table = findTableByAlias(table, editor); |
| 163 |
if (table !== oldTable) alias = true; |
| 164 |
} |
| 165 |
|
| 166 |
var columns = getTable(table); |
| 167 |
if (columns && columns.columns) |
| 168 |
columns = columns.columns; |
| 169 |
|
| 170 |
if (columns) { |
| 171 |
addMatches(result, string, columns, function(w) { |
| 172 |
var tableInsert = table; |
| 173 |
if (alias == true) tableInsert = aliasTable; |
| 174 |
if (typeof w == "string") { |
| 175 |
w = tableInsert + "." + w; |
| 176 |
} else { |
| 177 |
w = shallowClone(w); |
| 178 |
w.text = tableInsert + "." + w.text; |
| 179 |
} |
| 180 |
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w; |
| 181 |
}); |
| 182 |
} |
| 183 |
|
| 184 |
return start; |
| 185 |
} |
| 186 |
|
| 187 |
function eachWord(lineText, f) { |
| 188 |
var words = lineText.split(/\s+/) |
| 189 |
for (var i = 0; i < words.length; i++) |
| 190 |
if (words[i]) f(words[i].replace(/[,;]/g, '')) |
| 191 |
} |
| 192 |
|
| 193 |
function findTableByAlias(alias, editor) { |
| 194 |
var doc = editor.doc; |
| 195 |
var fullQuery = doc.getValue(); |
| 196 |
var aliasUpperCase = alias.toUpperCase(); |
| 197 |
var previousWord = ""; |
| 198 |
var table = ""; |
| 199 |
var separator = []; |
| 200 |
var validRange = { |
| 201 |
start: Pos(0, 0), |
| 202 |
end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length) |
| 203 |
}; |
| 204 |
|
| 205 |
//add separator |
| 206 |
var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV); |
| 207 |
while(indexOfSeparator != -1) { |
| 208 |
separator.push(doc.posFromIndex(indexOfSeparator)); |
| 209 |
indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1); |
| 210 |
} |
| 211 |
separator.unshift(Pos(0, 0)); |
| 212 |
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length)); |
| 213 |
|
| 214 |
//find valid range |
| 215 |
var prevItem = null; |
| 216 |
var current = editor.getCursor() |
| 217 |
for (var i = 0; i < separator.length; i++) { |
| 218 |
if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) { |
| 219 |
validRange = {start: prevItem, end: separator[i]}; |
| 220 |
break; |
| 221 |
} |
| 222 |
prevItem = separator[i]; |
| 223 |
} |
| 224 |
|
| 225 |
var query = doc.getRange(validRange.start, validRange.end, false); |
| 226 |
|
| 227 |
for (var i = 0; i < query.length; i++) { |
| 228 |
var lineText = query[i]; |
| 229 |
eachWord(lineText, function(word) { |
| 230 |
var wordUpperCase = word.toUpperCase(); |
| 231 |
if (wordUpperCase === aliasUpperCase && getTable(previousWord)) |
| 232 |
table = previousWord; |
| 233 |
if (wordUpperCase !== CONS.ALIAS_KEYWORD) |
| 234 |
previousWord = word; |
| 235 |
}); |
| 236 |
if (table) break; |
| 237 |
} |
| 238 |
return table; |
| 239 |
} |
| 240 |
|
| 241 |
CodeMirror.registerHelper("hint", "sql", function(editor, options) { |
| 242 |
tables = parseTables(options && options.tables) |
| 243 |
var defaultTableName = options && options.defaultTable; |
| 244 |
var disableKeywords = options && options.disableKeywords; |
| 245 |
defaultTable = defaultTableName && getTable(defaultTableName); |
| 246 |
keywords = getKeywords(editor); |
| 247 |
identifierQuote = getIdentifierQuote(editor); |
| 248 |
|
| 249 |
if (defaultTableName && !defaultTable) |
| 250 |
defaultTable = findTableByAlias(defaultTableName, editor); |
| 251 |
|
| 252 |
defaultTable = defaultTable || []; |
| 253 |
|
| 254 |
if (defaultTable.columns) |
| 255 |
defaultTable = defaultTable.columns; |
| 256 |
|
| 257 |
var cur = editor.getCursor(); |
| 258 |
var result = []; |
| 259 |
var token = editor.getTokenAt(cur), start, end, search; |
| 260 |
if (token.end > cur.ch) { |
| 261 |
token.end = cur.ch; |
| 262 |
token.string = token.string.slice(0, cur.ch - token.start); |
| 263 |
} |
| 264 |
|
| 265 |
if (token.string.match(/^[.`"\w@]\w*$/)) { |
| 266 |
search = token.string; |
| 267 |
start = token.start; |
| 268 |
end = token.end; |
| 269 |
} else { |
| 270 |
start = end = cur.ch; |
| 271 |
search = ""; |
| 272 |
} |
| 273 |
if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) { |
| 274 |
start = nameCompletion(cur, token, result, editor); |
| 275 |
} else { |
| 276 |
addMatches(result, search, tables, function(w) {return w;}); |
| 277 |
addMatches(result, search, defaultTable, function(w) {return w;}); |
| 278 |
if (!disableKeywords) |
| 279 |
addMatches(result, search, keywords, function(w) {return w.toUpperCase();}); |
| 280 |
} |
| 281 |
|
| 282 |
return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)}; |
| 283 |
}); |
| 284 |
}); |
| 285 |
|