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 CONS = { |
---|
18 | QUERY_DIV: ";", |
---|
19 | ALIAS_KEYWORD: "AS" |
---|
20 | }; |
---|
21 | var Pos = CodeMirror.Pos; |
---|
22 | |
---|
23 | function getKeywords(editor) { |
---|
24 | var mode = editor.doc.modeOption; |
---|
25 | if (mode === "sql") mode = "text/x-sql"; |
---|
26 | return CodeMirror.resolveMode(mode).keywords; |
---|
27 | } |
---|
28 | |
---|
29 | function getText(item) { |
---|
30 | return typeof item == "string" ? item : item.text; |
---|
31 | } |
---|
32 | |
---|
33 | function getItem(list, item) { |
---|
34 | if (!list.slice) return list[item]; |
---|
35 | for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item) |
---|
36 | return list[i]; |
---|
37 | } |
---|
38 | |
---|
39 | function shallowClone(object) { |
---|
40 | var result = {}; |
---|
41 | for (var key in object) if (object.hasOwnProperty(key)) |
---|
42 | result[key] = object[key]; |
---|
43 | return result; |
---|
44 | } |
---|
45 | |
---|
46 | function match(string, word) { |
---|
47 | var len = string.length; |
---|
48 | var sub = getText(word).substr(0, len); |
---|
49 | return string.toUpperCase() === sub.toUpperCase(); |
---|
50 | } |
---|
51 | |
---|
52 | function addMatches(result, search, wordlist, formatter) { |
---|
53 | for (var word in wordlist) { |
---|
54 | if (!wordlist.hasOwnProperty(word)) continue; |
---|
55 | if (Array.isArray(wordlist)) { |
---|
56 | word = wordlist[word]; |
---|
57 | } |
---|
58 | if (match(search, word)) { |
---|
59 | result.push(formatter(word)); |
---|
60 | } |
---|
61 | } |
---|
62 | } |
---|
63 | |
---|
64 | function cleanName(name) { |
---|
65 | // Get rid name from backticks(`) and preceding dot(.) |
---|
66 | if (name.charAt(0) == ".") { |
---|
67 | name = name.substr(1); |
---|
68 | } |
---|
69 | return name.replace(/`/g, ""); |
---|
70 | } |
---|
71 | |
---|
72 | function insertBackticks(name) { |
---|
73 | var nameParts = getText(name).split("."); |
---|
74 | for (var i = 0; i < nameParts.length; i++) |
---|
75 | nameParts[i] = "`" + nameParts[i] + "`"; |
---|
76 | var escaped = nameParts.join("."); |
---|
77 | if (typeof name == "string") return escaped; |
---|
78 | name = shallowClone(name); |
---|
79 | name.text = escaped; |
---|
80 | return name; |
---|
81 | } |
---|
82 | |
---|
83 | function nameCompletion(cur, token, result, editor) { |
---|
84 | // Try to complete table, colunm names and return start position of completion |
---|
85 | var useBacktick = false; |
---|
86 | var nameParts = []; |
---|
87 | var start = token.start; |
---|
88 | var cont = true; |
---|
89 | while (cont) { |
---|
90 | cont = (token.string.charAt(0) == "."); |
---|
91 | useBacktick = useBacktick || (token.string.charAt(0) == "`"); |
---|
92 | |
---|
93 | start = token.start; |
---|
94 | nameParts.unshift(cleanName(token.string)); |
---|
95 | |
---|
96 | token = editor.getTokenAt(Pos(cur.line, token.start)); |
---|
97 | if (token.string == ".") { |
---|
98 | cont = true; |
---|
99 | token = editor.getTokenAt(Pos(cur.line, token.start)); |
---|
100 | } |
---|
101 | } |
---|
102 | |
---|
103 | // Try to complete table names |
---|
104 | var string = nameParts.join("."); |
---|
105 | addMatches(result, string, tables, function(w) { |
---|
106 | return useBacktick ? insertBackticks(w) : w; |
---|
107 | }); |
---|
108 | |
---|
109 | // Try to complete columns from defaultTable |
---|
110 | addMatches(result, string, defaultTable, function(w) { |
---|
111 | return useBacktick ? insertBackticks(w) : w; |
---|
112 | }); |
---|
113 | |
---|
114 | // Try to complete columns |
---|
115 | string = nameParts.pop(); |
---|
116 | var table = nameParts.join("."); |
---|
117 | |
---|
118 | // Check if table is available. If not, find table by Alias |
---|
119 | if (!getItem(tables, table)) |
---|
120 | table = findTableByAlias(table, editor); |
---|
121 | |
---|
122 | var columns = getItem(tables, table); |
---|
123 | if (columns && Array.isArray(tables) && columns.columns) |
---|
124 | columns = columns.columns; |
---|
125 | |
---|
126 | if (columns) { |
---|
127 | addMatches(result, string, columns, function(w) { |
---|
128 | if (typeof w == "string") { |
---|
129 | w = table + "." + w; |
---|
130 | } else { |
---|
131 | w = shallowClone(w); |
---|
132 | w.text = table + "." + w.text; |
---|
133 | } |
---|
134 | return useBacktick ? insertBackticks(w) : w; |
---|
135 | }); |
---|
136 | } |
---|
137 | |
---|
138 | return start; |
---|
139 | } |
---|
140 | |
---|
141 | function eachWord(lineText, f) { |
---|
142 | if (!lineText) return; |
---|
143 | var excepted = /[,;]/g; |
---|
144 | var words = lineText.split(" "); |
---|
145 | for (var i = 0; i < words.length; i++) { |
---|
146 | f(words[i]?words[i].replace(excepted, '') : ''); |
---|
147 | } |
---|
148 | } |
---|
149 | |
---|
150 | function convertCurToNumber(cur) { |
---|
151 | // max characters of a line is 999,999. |
---|
152 | return cur.line + cur.ch / Math.pow(10, 6); |
---|
153 | } |
---|
154 | |
---|
155 | function convertNumberToCur(num) { |
---|
156 | return Pos(Math.floor(num), +num.toString().split('.').pop()); |
---|
157 | } |
---|
158 | |
---|
159 | function findTableByAlias(alias, editor) { |
---|
160 | var doc = editor.doc; |
---|
161 | var fullQuery = doc.getValue(); |
---|
162 | var aliasUpperCase = alias.toUpperCase(); |
---|
163 | var previousWord = ""; |
---|
164 | var table = ""; |
---|
165 | var separator = []; |
---|
166 | var validRange = { |
---|
167 | start: Pos(0, 0), |
---|
168 | end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length) |
---|
169 | }; |
---|
170 | |
---|
171 | //add separator |
---|
172 | var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV); |
---|
173 | while(indexOfSeparator != -1) { |
---|
174 | separator.push(doc.posFromIndex(indexOfSeparator)); |
---|
175 | indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1); |
---|
176 | } |
---|
177 | separator.unshift(Pos(0, 0)); |
---|
178 | separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length)); |
---|
179 | |
---|
180 | //find valid range |
---|
181 | var prevItem = 0; |
---|
182 | var current = convertCurToNumber(editor.getCursor()); |
---|
183 | for (var i=0; i< separator.length; i++) { |
---|
184 | var _v = convertCurToNumber(separator[i]); |
---|
185 | if (current > prevItem && current <= _v) { |
---|
186 | validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) }; |
---|
187 | break; |
---|
188 | } |
---|
189 | prevItem = _v; |
---|
190 | } |
---|
191 | |
---|
192 | var query = doc.getRange(validRange.start, validRange.end, false); |
---|
193 | |
---|
194 | for (var i = 0; i < query.length; i++) { |
---|
195 | var lineText = query[i]; |
---|
196 | eachWord(lineText, function(word) { |
---|
197 | var wordUpperCase = word.toUpperCase(); |
---|
198 | if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord)) |
---|
199 | table = previousWord; |
---|
200 | if (wordUpperCase !== CONS.ALIAS_KEYWORD) |
---|
201 | previousWord = word; |
---|
202 | }); |
---|
203 | if (table) break; |
---|
204 | } |
---|
205 | return table; |
---|
206 | } |
---|
207 | |
---|
208 | CodeMirror.registerHelper("hint", "sql", function(editor, options) { |
---|
209 | tables = (options && options.tables) || {}; |
---|
210 | var defaultTableName = options && options.defaultTable; |
---|
211 | defaultTable = (defaultTableName && getItem(tables, defaultTableName)) || []; |
---|
212 | keywords = keywords || getKeywords(editor); |
---|
213 | |
---|
214 | var cur = editor.getCursor(); |
---|
215 | var result = []; |
---|
216 | var token = editor.getTokenAt(cur), start, end, search; |
---|
217 | if (token.end > cur.ch) { |
---|
218 | token.end = cur.ch; |
---|
219 | token.string = token.string.slice(0, cur.ch - token.start); |
---|
220 | } |
---|
221 | |
---|
222 | if (token.string.match(/^[.`\w@]\w*$/)) { |
---|
223 | search = token.string; |
---|
224 | start = token.start; |
---|
225 | end = token.end; |
---|
226 | } else { |
---|
227 | start = end = cur.ch; |
---|
228 | search = ""; |
---|
229 | } |
---|
230 | if (search.charAt(0) == "." || search.charAt(0) == "`") { |
---|
231 | start = nameCompletion(cur, token, result, editor); |
---|
232 | } else { |
---|
233 | addMatches(result, search, tables, function(w) {return w;}); |
---|
234 | addMatches(result, search, defaultTable, function(w) {return w;}); |
---|
235 | addMatches(result, search, keywords, function(w) {return w.toUpperCase();}); |
---|
236 | } |
---|
237 | |
---|
238 | return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)}; |
---|
239 | }); |
---|
240 | }); |
---|