You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
9.4 KiB

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