Mercurial
comparison .cms/lib/codemirror/addon/hint/sql-hint.js @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others | |
2 // Distributed under an MIT license: https://codemirror.net/5/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 getModeConf(editor, field) { | |
27 return editor.getModeAt(editor.getCursor()).config[field] || CodeMirror.resolveMode("text/x-sql")[field] | |
28 } | |
29 | |
30 function getKeywords(editor) { | |
31 return getModeConf(editor, "keywords") || [] | |
32 } | |
33 | |
34 function getIdentifierQuote(editor) { | |
35 return getModeConf(editor, "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 duplicated 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 // duplicate 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 if (validRange.start) { | |
226 var query = doc.getRange(validRange.start, validRange.end, false); | |
227 | |
228 for (var i = 0; i < query.length; i++) { | |
229 var lineText = query[i]; | |
230 eachWord(lineText, function(word) { | |
231 var wordUpperCase = word.toUpperCase(); | |
232 if (wordUpperCase === aliasUpperCase && getTable(previousWord)) | |
233 table = previousWord; | |
234 if (wordUpperCase !== CONS.ALIAS_KEYWORD) | |
235 previousWord = word; | |
236 }); | |
237 if (table) break; | |
238 } | |
239 } | |
240 return table; | |
241 } | |
242 | |
243 CodeMirror.registerHelper("hint", "sql", function(editor, options) { | |
244 tables = parseTables(options && options.tables) | |
245 var defaultTableName = options && options.defaultTable; | |
246 var disableKeywords = options && options.disableKeywords; | |
247 defaultTable = defaultTableName && getTable(defaultTableName); | |
248 keywords = getKeywords(editor); | |
249 identifierQuote = getIdentifierQuote(editor); | |
250 | |
251 if (defaultTableName && !defaultTable) | |
252 defaultTable = findTableByAlias(defaultTableName, editor); | |
253 | |
254 defaultTable = defaultTable || []; | |
255 | |
256 if (defaultTable.columns) | |
257 defaultTable = defaultTable.columns; | |
258 | |
259 var cur = editor.getCursor(); | |
260 var result = []; | |
261 var token = editor.getTokenAt(cur), start, end, search; | |
262 if (token.end > cur.ch) { | |
263 token.end = cur.ch; | |
264 token.string = token.string.slice(0, cur.ch - token.start); | |
265 } | |
266 | |
267 if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) { | |
268 search = token.string; | |
269 start = token.start; | |
270 end = token.end; | |
271 } else { | |
272 start = end = cur.ch; | |
273 search = ""; | |
274 } | |
275 if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) { | |
276 start = nameCompletion(cur, token, result, editor); | |
277 } else { | |
278 var objectOrClass = function(w, className) { | |
279 if (typeof w === "object") { | |
280 w.className = className; | |
281 } else { | |
282 w = { text: w, className: className }; | |
283 } | |
284 return w; | |
285 }; | |
286 addMatches(result, search, defaultTable, function(w) { | |
287 return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table"); | |
288 }); | |
289 addMatches( | |
290 result, | |
291 search, | |
292 tables, function(w) { | |
293 return objectOrClass(w, "CodeMirror-hint-table"); | |
294 } | |
295 ); | |
296 if (!disableKeywords) | |
297 addMatches(result, search, keywords, function(w) { | |
298 return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword"); | |
299 }); | |
300 } | |
301 | |
302 return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)}; | |
303 }); | |
304 }); |