rfe #1595 make professional code editor suggestion

Signed-off-by: Zhang Hao <jason.daurus@gmail.com>
This commit is contained in:
Zhang Hao 2015-02-21 15:48:13 +08:00
parent 9f4a2b15a4
commit 2cc8a9ce3b
11 changed files with 2357 additions and 1113 deletions

View File

@ -14,7 +14,7 @@ $sql_autocomplete = array();
if ($db) {
$tableNames = $GLOBALS['dbi']->getTables($db);
foreach ($tableNames as $tableName) {
$sql_autocomplete[$tableName] = $GLOBALS['dbi']->getColumnNames(
$sql_autocomplete[$tableName] = $GLOBALS['dbi']->getColumns(
$db, $tableName
);
}

View File

@ -1,4 +1,4 @@
Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
Copyright (C) 2014 by Marijn Haverbeke <marijnh@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -17,7 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Please note that some subdirectories of the CodeMirror distribution
include their own LICENSE files, and are released under different
licences.

View File

@ -24,6 +24,18 @@
return cm.showHint(newOpts);
};
var asyncRunID = 0;
function retrieveHints(getter, cm, options, then) {
if (getter.async) {
var id = ++asyncRunID;
getter(cm, function(hints) {
if (asyncRunID == id) then(hints);
}, options);
} else {
then(getter(cm, options));
}
}
CodeMirror.defineExtension("showHint", function(options) {
// We want a single cursor position.
if (this.listSelections().length > 1 || this.somethingSelected()) return;
@ -34,10 +46,7 @@
if (!getHints) return;
CodeMirror.signal(this, "startCompletion", this);
if (getHints.async)
getHints(this, function(hints) { completion.showHints(hints); }, completion.options);
else
return completion.showHints(getHints(this, completion.options));
return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); });
});
function Completion(cm, options) {
@ -102,11 +111,7 @@
function update() {
if (finished) return;
CodeMirror.signal(data, "update");
var getHints = completion.options.hint;
if (getHints.async)
getHints(completion.cm, finishUpdate, completion.options);
else
finishUpdate(getHints(completion.cm, completion.options));
retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate);
}
function finishUpdate(data_) {
data = data_;
@ -243,7 +248,7 @@
}
}
}
var overlapX = box.left - winW;
var overlapX = box.right - winW;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";

View File

@ -26,9 +26,26 @@
return CodeMirror.resolveMode(mode).keywords;
}
function getText(item) {
return typeof item == "string" ? item : item.text;
}
function getItem(list, item) {
if (!list.slice) return list[item];
for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item)
return list[i];
}
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 = word.substr(0, len);
var sub = getText(word).substr(0, len);
return string.toUpperCase() === sub.toUpperCase();
}
@ -44,55 +61,81 @@
}
}
function nameCompletion(result, editor) {
var cur = editor.getCursor();
var token = editor.getTokenAt(cur);
var useBacktick = (token.string.charAt(0) == "`");
var string = token.string.substr(1);
var prevToken = editor.getTokenAt(Pos(cur.line, token.start));
if (token.string.charAt(0) == "." || prevToken.string == "."){
//Suggest colunm names
if (prevToken.string == ".") {
var prevToken = editor.getTokenAt(Pos(cur.line, token.start - 1));
}
var table = prevToken.string;
//Check if backtick is used in table name. If yes, use it for columns too.
var useBacktickTable = false;
if (table.match(/`/g)) {
useBacktickTable = true;
table = table.replace(/`/g, "");
}
//Check if table is available. If not, find table by Alias
if (!tables.hasOwnProperty(table))
table = findTableByAlias(table, editor);
var columns = tables[table];
if (!columns) return;
function cleanName(name) {
// Get rid name from backticks(`) and preceding dot(.)
if (name.charAt(0) == ".") {
name = name.substr(1);
}
return name.replace(/`/g, "");
}
if (useBacktick) {
addMatches(result, string, columns, function(w) {return "`" + w + "`";});
}
else if(useBacktickTable) {
addMatches(result, string, columns, function(w) {return ".`" + w + "`";});
}
else {
addMatches(result, string, columns, function(w) {return "." + w;});
function insertBackticks(name) {
var nameParts = getText(name).split(".");
for (var i = 0; i < nameParts.length; i++)
nameParts[i] = "`" + nameParts[i] + "`";
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, colunm names and return start position of completion
var useBacktick = false;
var nameParts = [];
var start = token.start;
var cont = true;
while (cont) {
cont = (token.string.charAt(0) == ".");
useBacktick = useBacktick || (token.string.charAt(0) == "`");
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));
}
}
else {
//Suggest table names or colums in defaultTable
while (token.start && string.charAt(0) == ".") {
token = editor.getTokenAt(Pos(cur.line, token.start - 1));
string = token.string + string;
}
if (useBacktick) {
addMatches(result, string, tables, function(w) {return "`" + w + "`";});
addMatches(result, string, defaultTable, function(w) {return "`" + w + "`";});
}
else {
addMatches(result, string, tables, function(w) {return w;});
addMatches(result, string, defaultTable, function(w) {return w;});
}
// Try to complete table names
var string = nameParts.join(".");
addMatches(result, string, tables, function(w) {
return useBacktick ? insertBackticks(w) : w;
});
// Try to complete columns from defaultTable
addMatches(result, string, defaultTable, function(w) {
return useBacktick ? insertBackticks(w) : w;
});
// Try to complete columns
string = nameParts.pop();
var table = nameParts.join(".");
// Check if table is available. If not, find table by Alias
if (!getItem(tables, table))
table = findTableByAlias(table, editor);
var columns = getItem(tables, table);
if (columns && Array.isArray(tables) && columns.columns)
columns = columns.columns;
if (columns) {
addMatches(result, string, columns, function(w) {
if (typeof w == "string") {
w = table + "." + w;
} else {
w = shallowClone(w);
w.text = table + "." + w.text;
}
return useBacktick ? insertBackticks(w) : w;
});
}
return start;
}
function eachWord(lineText, f) {
@ -152,12 +195,10 @@
var lineText = query[i];
eachWord(lineText, function(word) {
var wordUpperCase = word.toUpperCase();
if (wordUpperCase === aliasUpperCase && tables.hasOwnProperty(previousWord)) {
table = previousWord;
}
if (wordUpperCase !== CONS.ALIAS_KEYWORD) {
if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord))
table = previousWord;
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
previousWord = word;
}
});
if (table) break;
}
@ -167,12 +208,25 @@
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
tables = (options && options.tables) || {};
var defaultTableName = options && options.defaultTable;
defaultTable = (defaultTableName && tables[defaultTableName] || []);
defaultTable = defaultTableName && getItem(tables, defaultTableName);
keywords = keywords || getKeywords(editor);
if (defaultTableName && !defaultTable)
defaultTable = findTableByAlias(defaultTableName, editor);
defaultTable = defaultTable || [];
if (Array.isArray(tables) && 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;
@ -182,7 +236,7 @@
search = "";
}
if (search.charAt(0) == "." || search.charAt(0) == "`") {
nameCompletion(result, editor);
start = nameCompletion(cur, token, result, editor);
} else {
addMatches(result, search, tables, function(w) {return w;});
addMatches(result, search, defaultTable, function(w) {return w;});

View File

@ -1,3 +1,6 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@ -57,7 +60,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new CodeMirror.StringStream(lines[i]);
if (!stream.string && mode.blankLine) mode.blankLine();
if (!stream.string && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
var style = mode.token(stream, state);
callback(stream.current(), style, i, stream.start, state);

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,6 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// TODO actually recognize syntax of TypeScript constructs
(function(mod) {
@ -16,6 +19,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
@ -114,7 +118,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (state.lastType == "operator" || state.lastType == "keyword c" ||
state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
readRegexp(stream);
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
@ -129,8 +133,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else {
stream.eatWhile(/[\w\$_]/);
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
@ -199,8 +203,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (--depth == 0) break;
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (/[$\w]/.test(ch)) {
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (sawSomething && !depth) {
++pos;
break;
@ -235,7 +241,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
@ -295,6 +301,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
@ -340,7 +348,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
if (type == "class") return cont(pushlex("form"), className, objlit, poplex);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("form"), afterExport, poplex);
if (type == "import") return cont(pushlex("form"), afterImport, poplex);
return pass(pushlex("stat"), expression, expect(";"), poplex);
@ -385,7 +393,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (value == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
@ -411,13 +419,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
if (type == "{") return pass(statement);
return pass(expression);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
if (type == "{") return pass(statement);
return pass(expressionNoComma);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
@ -427,15 +433,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "variable") {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (type + " property");
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
}
if (atomicTypes.hasOwnProperty(type)) return cont(afterprop);
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
@ -534,11 +543,27 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(_type, value) {
if (value == "extends") return cont(expression);
function classNameAfter(type, value) {
if (value == "extends") return cont(expression, classNameAfter);
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function objlit(type) {
if (type == "{") return contCommasep(objprop, "}");
function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
return cont(functiondef, classBody);
}
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (type == ";") return cont(classBody);
if (type == "}") return cont();
}
function classGetterSetter(type) {
if (type != "variable") return pass();
cx.marked = "property";
return cont();
}
function afterModule(type, value) {
if (type == "string") return cont(statement);
@ -567,7 +592,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(expressionNoComma, "]"));
if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
@ -575,6 +600,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "if") return cont(expression, comprehension);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
// Interface
return {
@ -626,14 +657,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}",
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
@ -645,9 +676,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});

View File

@ -1,3 +1,6 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@ -187,7 +190,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
indent: function(state, textAfter) {
var cx = state.context;
if (!cx) return 0;
if (!cx) return CodeMirror.Pass;
var closing = textAfter.charAt(0) == cx.type;
if (cx.align) return cx.col + (closing ? 0 : 1);
else return cx.indent + (closing ? 0 : config.indentUnit);
@ -210,7 +213,8 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
while ((ch = stream.next()) != null) {
if (ch == "`" && !stream.eat("`")) return "variable-2";
}
return null;
stream.backUp(stream.current().length - 1);
return stream.eatWhile(/\w/) ? "variable-2" : null;
}
// variable token
@ -363,8 +367,6 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
keywords:
A list of keywords you want to be highlighted.
functions:
A list of function names you want to be highlighted.
builtin:
A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword").
operatorChars:

View File

@ -1,3 +1,6 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@ -18,7 +21,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true},
'track': true, 'wbr': true, 'menuitem': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
@ -121,7 +124,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " error" : "error";
return next ? next + " tag error" : "tag error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
state.stringStartCol = stream.column();

View File

@ -1732,7 +1732,32 @@ function codemirrorAutocompleteOnInputRead(instance) {
if (data.success) {
sql_autocomplete = $.parseJSON(data.tables);
sql_autocomplete_default_table = PMA_commonParams.get('table');
instance.options.hintOptions.tables = sql_autocomplete;
var result = [];
for (var table in sql_autocomplete) {
if (sql_autocomplete.hasOwnProperty(table)) {
var columns = sql_autocomplete[table];
table = {
text: table,
columns: []
};
for (var column in columns) {
if (columns.hasOwnProperty(column)) {
var displayText = column + ' | ' + columns[column].Type;
if (columns[column].Key == 'PRI') {
displayText += ' | Primary';
} else if (columns[column].Key == 'UNI') {
displayText += ' | Unique';
}
table.columns.push({
text: column,
displayText: displayText
});
}
}
}
result.push(table);
}
instance.options.hintOptions.tables = result;
instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
}
}

View File

@ -176,6 +176,9 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-wrapper {
position: relative;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;