305 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// CodeMirror, copyright (c) by Marijn Haverbeke and others
 | 
						|
// Distributed under an MIT license: https://codemirror.net/LICENSE
 | 
						|
 | 
						|
(function(mod) {
 | 
						|
  if (typeof exports == "object" && typeof module == "object") // CommonJS
 | 
						|
    mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
 | 
						|
  else if (typeof define == "function" && define.amd) // AMD
 | 
						|
    define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
 | 
						|
  else // Plain browser env
 | 
						|
    mod(CodeMirror);
 | 
						|
})(function(CodeMirror) {
 | 
						|
  "use strict";
 | 
						|
 | 
						|
  var tables;
 | 
						|
  var defaultTable;
 | 
						|
  var keywords;
 | 
						|
  var identifierQuote;
 | 
						|
  var CONS = {
 | 
						|
    QUERY_DIV: ";",
 | 
						|
    ALIAS_KEYWORD: "AS"
 | 
						|
  };
 | 
						|
  var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
 | 
						|
 | 
						|
  function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
 | 
						|
 | 
						|
  function getKeywords(editor) {
 | 
						|
    var mode = editor.doc.modeOption;
 | 
						|
    if (mode === "sql") mode = "text/x-sql";
 | 
						|
    return CodeMirror.resolveMode(mode).keywords;
 | 
						|
  }
 | 
						|
 | 
						|
  function getIdentifierQuote(editor) {
 | 
						|
    var mode = editor.doc.modeOption;
 | 
						|
    if (mode === "sql") mode = "text/x-sql";
 | 
						|
    return CodeMirror.resolveMode(mode).identifierQuote || "`";
 | 
						|
  }
 | 
						|
 | 
						|
  function getText(item) {
 | 
						|
    return typeof item == "string" ? item : item.text;
 | 
						|
  }
 | 
						|
 | 
						|
  function wrapTable(name, value) {
 | 
						|
    if (isArray(value)) value = {columns: value}
 | 
						|
    if (!value.text) value.text = name
 | 
						|
    return value
 | 
						|
  }
 | 
						|
 | 
						|
  function parseTables(input) {
 | 
						|
    var result = {}
 | 
						|
    if (isArray(input)) {
 | 
						|
      for (var i = input.length - 1; i >= 0; i--) {
 | 
						|
        var item = input[i]
 | 
						|
        result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
 | 
						|
      }
 | 
						|
    } else if (input) {
 | 
						|
      for (var name in input)
 | 
						|
        result[name.toUpperCase()] = wrapTable(name, input[name])
 | 
						|
    }
 | 
						|
    return result
 | 
						|
  }
 | 
						|
 | 
						|
  function getTable(name) {
 | 
						|
    return tables[name.toUpperCase()]
 | 
						|
  }
 | 
						|
 | 
						|
  function shallowClone(object) {
 | 
						|
    var result = {};
 | 
						|
    for (var key in object) if (object.hasOwnProperty(key))
 | 
						|
      result[key] = object[key];
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  function match(string, word) {
 | 
						|
    var len = string.length;
 | 
						|
    var sub = getText(word).substr(0, len);
 | 
						|
    return string.toUpperCase() === sub.toUpperCase();
 | 
						|
  }
 | 
						|
 | 
						|
  function addMatches(result, search, wordlist, formatter) {
 | 
						|
    if (isArray(wordlist)) {
 | 
						|
      for (var i = 0; i < wordlist.length; i++)
 | 
						|
        if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
 | 
						|
    } else {
 | 
						|
      for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
 | 
						|
        var val = wordlist[word]
 | 
						|
        if (!val || val === true)
 | 
						|
          val = word
 | 
						|
        else
 | 
						|
          val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
 | 
						|
        if (match(search, val)) result.push(formatter(val))
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function cleanName(name) {
 | 
						|
    // Get rid name from identifierQuote and preceding dot(.)
 | 
						|
    if (name.charAt(0) == ".") {
 | 
						|
      name = name.substr(1);
 | 
						|
    }
 | 
						|
    // replace doublicated identifierQuotes with single identifierQuotes
 | 
						|
    // and remove single identifierQuotes
 | 
						|
    var nameParts = name.split(identifierQuote+identifierQuote);
 | 
						|
    for (var i = 0; i < nameParts.length; i++)
 | 
						|
      nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
 | 
						|
    return nameParts.join(identifierQuote);
 | 
						|
  }
 | 
						|
 | 
						|
  function insertIdentifierQuotes(name) {
 | 
						|
    var nameParts = getText(name).split(".");
 | 
						|
    for (var i = 0; i < nameParts.length; i++)
 | 
						|
      nameParts[i] = identifierQuote +
 | 
						|
        // doublicate identifierQuotes
 | 
						|
        nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
 | 
						|
        identifierQuote;
 | 
						|
    var escaped = nameParts.join(".");
 | 
						|
    if (typeof name == "string") return escaped;
 | 
						|
    name = shallowClone(name);
 | 
						|
    name.text = escaped;
 | 
						|
    return name;
 | 
						|
  }
 | 
						|
 | 
						|
  function nameCompletion(cur, token, result, editor) {
 | 
						|
    // Try to complete table, column names and return start position of completion
 | 
						|
    var useIdentifierQuotes = false;
 | 
						|
    var nameParts = [];
 | 
						|
    var start = token.start;
 | 
						|
    var cont = true;
 | 
						|
    while (cont) {
 | 
						|
      cont = (token.string.charAt(0) == ".");
 | 
						|
      useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
 | 
						|
 | 
						|
      start = token.start;
 | 
						|
      nameParts.unshift(cleanName(token.string));
 | 
						|
 | 
						|
      token = editor.getTokenAt(Pos(cur.line, token.start));
 | 
						|
      if (token.string == ".") {
 | 
						|
        cont = true;
 | 
						|
        token = editor.getTokenAt(Pos(cur.line, token.start));
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Try to complete table names
 | 
						|
    var string = nameParts.join(".");
 | 
						|
    addMatches(result, string, tables, function(w) {
 | 
						|
      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
 | 
						|
    });
 | 
						|
 | 
						|
    // Try to complete columns from defaultTable
 | 
						|
    addMatches(result, string, defaultTable, function(w) {
 | 
						|
      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
 | 
						|
    });
 | 
						|
 | 
						|
    // Try to complete columns
 | 
						|
    string = nameParts.pop();
 | 
						|
    var table = nameParts.join(".");
 | 
						|
 | 
						|
    var alias = false;
 | 
						|
    var aliasTable = table;
 | 
						|
    // Check if table is available. If not, find table by Alias
 | 
						|
    if (!getTable(table)) {
 | 
						|
      var oldTable = table;
 | 
						|
      table = findTableByAlias(table, editor);
 | 
						|
      if (table !== oldTable) alias = true;
 | 
						|
    }
 | 
						|
 | 
						|
    var columns = getTable(table);
 | 
						|
    if (columns && columns.columns)
 | 
						|
      columns = columns.columns;
 | 
						|
 | 
						|
    if (columns) {
 | 
						|
      addMatches(result, string, columns, function(w) {
 | 
						|
        var tableInsert = table;
 | 
						|
        if (alias == true) tableInsert = aliasTable;
 | 
						|
        if (typeof w == "string") {
 | 
						|
          w = tableInsert + "." + w;
 | 
						|
        } else {
 | 
						|
          w = shallowClone(w);
 | 
						|
          w.text = tableInsert + "." + w.text;
 | 
						|
        }
 | 
						|
        return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    return start;
 | 
						|
  }
 | 
						|
 | 
						|
  function eachWord(lineText, f) {
 | 
						|
    var words = lineText.split(/\s+/)
 | 
						|
    for (var i = 0; i < words.length; i++)
 | 
						|
      if (words[i]) f(words[i].replace(/[,;]/g, ''))
 | 
						|
  }
 | 
						|
 | 
						|
  function findTableByAlias(alias, editor) {
 | 
						|
    var doc = editor.doc;
 | 
						|
    var fullQuery = doc.getValue();
 | 
						|
    var aliasUpperCase = alias.toUpperCase();
 | 
						|
    var previousWord = "";
 | 
						|
    var table = "";
 | 
						|
    var separator = [];
 | 
						|
    var validRange = {
 | 
						|
      start: Pos(0, 0),
 | 
						|
      end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
 | 
						|
    };
 | 
						|
 | 
						|
    //add separator
 | 
						|
    var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
 | 
						|
    while(indexOfSeparator != -1) {
 | 
						|
      separator.push(doc.posFromIndex(indexOfSeparator));
 | 
						|
      indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
 | 
						|
    }
 | 
						|
    separator.unshift(Pos(0, 0));
 | 
						|
    separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
 | 
						|
 | 
						|
    //find valid range
 | 
						|
    var prevItem = null;
 | 
						|
    var current = editor.getCursor()
 | 
						|
    for (var i = 0; i < separator.length; i++) {
 | 
						|
      if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
 | 
						|
        validRange = {start: prevItem, end: separator[i]};
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      prevItem = separator[i];
 | 
						|
    }
 | 
						|
 | 
						|
    if (validRange.start) {
 | 
						|
      var query = doc.getRange(validRange.start, validRange.end, false);
 | 
						|
 | 
						|
      for (var i = 0; i < query.length; i++) {
 | 
						|
        var lineText = query[i];
 | 
						|
        eachWord(lineText, function(word) {
 | 
						|
          var wordUpperCase = word.toUpperCase();
 | 
						|
          if (wordUpperCase === aliasUpperCase && getTable(previousWord))
 | 
						|
            table = previousWord;
 | 
						|
          if (wordUpperCase !== CONS.ALIAS_KEYWORD)
 | 
						|
            previousWord = word;
 | 
						|
        });
 | 
						|
        if (table) break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return table;
 | 
						|
  }
 | 
						|
 | 
						|
  CodeMirror.registerHelper("hint", "sql", function(editor, options) {
 | 
						|
    tables = parseTables(options && options.tables)
 | 
						|
    var defaultTableName = options && options.defaultTable;
 | 
						|
    var disableKeywords = options && options.disableKeywords;
 | 
						|
    defaultTable = defaultTableName && getTable(defaultTableName);
 | 
						|
    keywords = getKeywords(editor);
 | 
						|
    identifierQuote = getIdentifierQuote(editor);
 | 
						|
 | 
						|
    if (defaultTableName && !defaultTable)
 | 
						|
      defaultTable = findTableByAlias(defaultTableName, editor);
 | 
						|
 | 
						|
    defaultTable = defaultTable || [];
 | 
						|
 | 
						|
    if (defaultTable.columns)
 | 
						|
      defaultTable = defaultTable.columns;
 | 
						|
 | 
						|
    var cur = editor.getCursor();
 | 
						|
    var result = [];
 | 
						|
    var token = editor.getTokenAt(cur), start, end, search;
 | 
						|
    if (token.end > cur.ch) {
 | 
						|
      token.end = cur.ch;
 | 
						|
      token.string = token.string.slice(0, cur.ch - token.start);
 | 
						|
    }
 | 
						|
 | 
						|
    if (token.string.match(/^[.`"\w@]\w*$/)) {
 | 
						|
      search = token.string;
 | 
						|
      start = token.start;
 | 
						|
      end = token.end;
 | 
						|
    } else {
 | 
						|
      start = end = cur.ch;
 | 
						|
      search = "";
 | 
						|
    }
 | 
						|
    if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
 | 
						|
      start = nameCompletion(cur, token, result, editor);
 | 
						|
    } else {
 | 
						|
      var objectOrClass = function(w, className) {
 | 
						|
        if (typeof w === "object") {
 | 
						|
          w.className = className;
 | 
						|
        } else {
 | 
						|
          w = { text: w, className: className };
 | 
						|
        }
 | 
						|
        return w;
 | 
						|
      };
 | 
						|
    addMatches(result, search, defaultTable, function(w) {
 | 
						|
        return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
 | 
						|
    });
 | 
						|
    addMatches(
 | 
						|
        result,
 | 
						|
        search,
 | 
						|
        tables, function(w) {
 | 
						|
          return objectOrClass(w, "CodeMirror-hint-table");
 | 
						|
        }
 | 
						|
    );
 | 
						|
    if (!disableKeywords)
 | 
						|
      addMatches(result, search, keywords, function(w) {
 | 
						|
          return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
 | 
						|
      });
 | 
						|
  }
 | 
						|
 | 
						|
    return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
 | 
						|
  });
 | 
						|
});
 |