Skip to content

Commit e0fccd6

Browse files
committed
add autocomplete and bracket close
1 parent 7db6eca commit e0fccd6

8 files changed

Lines changed: 913 additions & 29 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2+
// Distributed under an MIT license: https://codemirror.net/LICENSE
3+
4+
(function(mod) {
5+
if (typeof exports == "object" && typeof module == "object") // CommonJS
6+
mod(require("../../lib/codemirror"));
7+
else if (typeof define == "function" && define.amd) // AMD
8+
define(["../../lib/codemirror"], mod);
9+
else // Plain browser env
10+
mod(CodeMirror);
11+
})(function(CodeMirror) {
12+
var defaults = {
13+
pairs: "()[]{}''\"\"",
14+
closeBefore: ")]}'\":;>",
15+
triples: "",
16+
explode: "[]{}"
17+
};
18+
19+
var Pos = CodeMirror.Pos;
20+
21+
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
22+
if (old && old != CodeMirror.Init) {
23+
cm.removeKeyMap(keyMap);
24+
cm.state.closeBrackets = null;
25+
}
26+
if (val) {
27+
ensureBound(getOption(val, "pairs"))
28+
cm.state.closeBrackets = val;
29+
cm.addKeyMap(keyMap);
30+
}
31+
});
32+
33+
function getOption(conf, name) {
34+
if (name == "pairs" && typeof conf == "string") return conf;
35+
if (typeof conf == "object" && conf[name] != null) return conf[name];
36+
return defaults[name];
37+
}
38+
39+
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
40+
function ensureBound(chars) {
41+
for (var i = 0; i < chars.length; i++) {
42+
var ch = chars.charAt(i), key = "'" + ch + "'"
43+
if (!keyMap[key]) keyMap[key] = handler(ch)
44+
}
45+
}
46+
ensureBound(defaults.pairs + "`")
47+
48+
function handler(ch) {
49+
return function(cm) { return handleChar(cm, ch); };
50+
}
51+
52+
function getConfig(cm) {
53+
var deflt = cm.state.closeBrackets;
54+
if (!deflt || deflt.override) return deflt;
55+
var mode = cm.getModeAt(cm.getCursor());
56+
return mode.closeBrackets || deflt;
57+
}
58+
59+
function handleBackspace(cm) {
60+
var conf = getConfig(cm);
61+
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
62+
63+
var pairs = getOption(conf, "pairs");
64+
var ranges = cm.listSelections();
65+
for (var i = 0; i < ranges.length; i++) {
66+
if (!ranges[i].empty()) return CodeMirror.Pass;
67+
var around = charsAround(cm, ranges[i].head);
68+
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
69+
}
70+
for (var i = ranges.length - 1; i >= 0; i--) {
71+
var cur = ranges[i].head;
72+
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
73+
}
74+
}
75+
76+
function handleEnter(cm) {
77+
var conf = getConfig(cm);
78+
var explode = conf && getOption(conf, "explode");
79+
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
80+
81+
var ranges = cm.listSelections();
82+
for (var i = 0; i < ranges.length; i++) {
83+
if (!ranges[i].empty()) return CodeMirror.Pass;
84+
var around = charsAround(cm, ranges[i].head);
85+
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
86+
}
87+
cm.operation(function() {
88+
var linesep = cm.lineSeparator() || "\n";
89+
cm.replaceSelection(linesep + linesep, null);
90+
cm.execCommand("goCharLeft");
91+
ranges = cm.listSelections();
92+
for (var i = 0; i < ranges.length; i++) {
93+
var line = ranges[i].head.line;
94+
cm.indentLine(line, null, true);
95+
cm.indentLine(line + 1, null, true);
96+
}
97+
});
98+
}
99+
100+
function contractSelection(sel) {
101+
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
102+
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
103+
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
104+
}
105+
106+
function handleChar(cm, ch) {
107+
var conf = getConfig(cm);
108+
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
109+
110+
var pairs = getOption(conf, "pairs");
111+
var pos = pairs.indexOf(ch);
112+
if (pos == -1) return CodeMirror.Pass;
113+
114+
var closeBefore = getOption(conf,"closeBefore");
115+
116+
var triples = getOption(conf, "triples");
117+
118+
var identical = pairs.charAt(pos + 1) == ch;
119+
var ranges = cm.listSelections();
120+
var opening = pos % 2 == 0;
121+
122+
var type;
123+
for (var i = 0; i < ranges.length; i++) {
124+
var range = ranges[i], cur = range.head, curType;
125+
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
126+
if (opening && !range.empty()) {
127+
curType = "surround";
128+
} else if ((identical || !opening) && next == ch) {
129+
if (identical && stringStartsAfter(cm, cur))
130+
curType = "both";
131+
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
132+
curType = "skipThree";
133+
else
134+
curType = "skip";
135+
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
136+
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
137+
if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
138+
curType = "addFour";
139+
} else if (identical) {
140+
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
141+
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
142+
else return CodeMirror.Pass;
143+
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
144+
curType = "both";
145+
} else {
146+
return CodeMirror.Pass;
147+
}
148+
if (!type) type = curType;
149+
else if (type != curType) return CodeMirror.Pass;
150+
}
151+
152+
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
153+
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
154+
cm.operation(function() {
155+
if (type == "skip") {
156+
cm.execCommand("goCharRight");
157+
} else if (type == "skipThree") {
158+
for (var i = 0; i < 3; i++)
159+
cm.execCommand("goCharRight");
160+
} else if (type == "surround") {
161+
var sels = cm.getSelections();
162+
for (var i = 0; i < sels.length; i++)
163+
sels[i] = left + sels[i] + right;
164+
cm.replaceSelections(sels, "around");
165+
sels = cm.listSelections().slice();
166+
for (var i = 0; i < sels.length; i++)
167+
sels[i] = contractSelection(sels[i]);
168+
cm.setSelections(sels);
169+
} else if (type == "both") {
170+
cm.replaceSelection(left + right, null);
171+
cm.triggerElectric(left + right);
172+
cm.execCommand("goCharLeft");
173+
} else if (type == "addFour") {
174+
cm.replaceSelection(left + left + left + left, "before");
175+
cm.execCommand("goCharRight");
176+
}
177+
});
178+
}
179+
180+
function charsAround(cm, pos) {
181+
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
182+
Pos(pos.line, pos.ch + 1));
183+
return str.length == 2 ? str : null;
184+
}
185+
186+
function stringStartsAfter(cm, pos) {
187+
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
188+
return /\bstring/.test(token.type) && token.start == pos.ch &&
189+
(pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
190+
}
191+
});
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2+
// Distributed under an MIT license: https://codemirror.net/LICENSE
3+
4+
(function(mod) {
5+
if (typeof exports == "object" && typeof module == "object") // CommonJS
6+
mod(require("../../lib/codemirror"));
7+
else if (typeof define == "function" && define.amd) // AMD
8+
define(["../../lib/codemirror"], mod);
9+
else // Plain browser env
10+
mod(CodeMirror);
11+
})(function(CodeMirror) {
12+
var Pos = CodeMirror.Pos;
13+
14+
function forEach(arr, f) {
15+
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
16+
}
17+
18+
function arrayContains(arr, item) {
19+
if (!Array.prototype.indexOf) {
20+
var i = arr.length;
21+
while (i--) {
22+
if (arr[i] === item) {
23+
return true;
24+
}
25+
}
26+
return false;
27+
}
28+
return arr.indexOf(item) != -1;
29+
}
30+
31+
function scriptHint(editor, keywords, getToken, options) {
32+
// Find the token at the cursor
33+
var cur = editor.getCursor(), token = getToken(editor, cur);
34+
if (/\b(?:string|comment)\b/.test(token.type)) return;
35+
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
36+
if (innerMode.mode.helperType === "json") return;
37+
token.state = innerMode.state;
38+
39+
// If it's not a 'word-style' token, ignore the token.
40+
if (!/^[\w$_]*$/.test(token.string)) {
41+
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
42+
type: token.string == "." ? "property" : null};
43+
} else if (token.end > cur.ch) {
44+
token.end = cur.ch;
45+
token.string = token.string.slice(0, cur.ch - token.start);
46+
}
47+
48+
var tprop = token;
49+
// If it is a property, find out what it is a property of.
50+
while (tprop.type == "property") {
51+
tprop = getToken(editor, Pos(cur.line, tprop.start));
52+
if (tprop.string != ".") return;
53+
tprop = getToken(editor, Pos(cur.line, tprop.start));
54+
if (!context) var context = [];
55+
context.push(tprop);
56+
}
57+
return {list: getCompletions(token, context, keywords, options),
58+
from: Pos(cur.line, token.start),
59+
to: Pos(cur.line, token.end)};
60+
}
61+
62+
function javascriptHint(editor, options) {
63+
return scriptHint(editor, javascriptKeywords,
64+
function (e, cur) {return e.getTokenAt(cur);},
65+
options);
66+
};
67+
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
68+
69+
function getCoffeeScriptToken(editor, cur) {
70+
// This getToken, it is for coffeescript, imitates the behavior of
71+
// getTokenAt method in javascript.js, that is, returning "property"
72+
// type and treat "." as indepenent token.
73+
var token = editor.getTokenAt(cur);
74+
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
75+
token.end = token.start;
76+
token.string = '.';
77+
token.type = "property";
78+
}
79+
else if (/^\.[\w$_]*$/.test(token.string)) {
80+
token.type = "property";
81+
token.start++;
82+
token.string = token.string.replace(/\./, '');
83+
}
84+
return token;
85+
}
86+
87+
function coffeescriptHint(editor, options) {
88+
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
89+
}
90+
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
91+
92+
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
93+
"toUpperCase toLowerCase split concat match replace search").split(" ");
94+
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
95+
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
96+
var funcProps = "prototype apply call bind".split(" ");
97+
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
98+
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
99+
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
100+
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
101+
102+
function forAllProps(obj, callback) {
103+
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
104+
for (var name in obj) callback(name)
105+
} else {
106+
for (var o = obj; o; o = Object.getPrototypeOf(o))
107+
Object.getOwnPropertyNames(o).forEach(callback)
108+
}
109+
}
110+
111+
function getCompletions(token, context, keywords, options) {
112+
var found = [], start = token.string, global = options && options.globalScope || window;
113+
function maybeAdd(str) {
114+
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
115+
}
116+
function gatherCompletions(obj) {
117+
if (typeof obj == "string") forEach(stringProps, maybeAdd);
118+
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
119+
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
120+
forAllProps(obj, maybeAdd)
121+
}
122+
123+
if (context && context.length) {
124+
// If this is a property, see if it belongs to some object we can
125+
// find in the current environment.
126+
var obj = context.pop(), base;
127+
if (obj.type && obj.type.indexOf("variable") === 0) {
128+
if (options && options.additionalContext)
129+
base = options.additionalContext[obj.string];
130+
if (!options || options.useGlobalScope !== false)
131+
base = base || global[obj.string];
132+
} else if (obj.type == "string") {
133+
base = "";
134+
} else if (obj.type == "atom") {
135+
base = 1;
136+
} else if (obj.type == "function") {
137+
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
138+
(typeof global.jQuery == 'function'))
139+
base = global.jQuery();
140+
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
141+
base = global._();
142+
}
143+
while (base != null && context.length)
144+
base = base[context.pop().string];
145+
if (base != null) gatherCompletions(base);
146+
} else {
147+
// If not, just look in the global object, any local scope, and optional additional-context
148+
// (reading into JS mode internals to get at the local and global variables)
149+
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
150+
for (var c = token.state.context; c; c = c.prev)
151+
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
152+
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
153+
if (options && options.additionalContext != null)
154+
for (var key in options.additionalContext)
155+
maybeAdd(key);
156+
if (!options || options.useGlobalScope !== false)
157+
gatherCompletions(global);
158+
forEach(keywords, maybeAdd);
159+
}
160+
return found;
161+
}
162+
});

0 commit comments

Comments
 (0)