From 6d5ee1f1d5647db1091ca6aa8d30afbd56d12c20 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 20 Nov 2013 13:47:32 +0000 Subject: [PATCH] feature(edit) add autocompletion --- lib/client/edit.js | 9 +- lib/client/edit/ext-language_tools.js | 1429 ++++++++++++++++++++++++ lib/client/edit/snippets/javascript.js | 202 ++++ 3 files changed, 1639 insertions(+), 1 deletion(-) create mode 100644 lib/client/edit/ext-language_tools.js create mode 100644 lib/client/edit/snippets/javascript.js diff --git a/lib/client/edit.js b/lib/client/edit.js index 1adee29e..fdb2d3ec 100644 --- a/lib/client/edit.js +++ b/lib/client/edit.js @@ -61,7 +61,11 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch; initAce(); } - Ace.session.setOption('useWorker', lUseWorker); + Ace.setOptions({ + useWorker : lUseWorker, + enableBasicAutocompletion : true, + enableSnippets : true + }); if (Util.isString(pValue)) { Ace.setValue(pValue); @@ -97,6 +101,8 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch; function initAce() { var lSession; + ace.require('ace/ext/language_tools'); + Ace = ace.edit(Element); lSession = Ace.getSession(); @@ -166,6 +172,7 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch; var lFiles = [ DIR + 'theme-tomorrow_night_blue.js', + DIR + 'ext-language_tools.js', DIR + 'ace.js', ]; diff --git a/lib/client/edit/ext-language_tools.js b/lib/client/edit/ext-language_tools.js new file mode 100644 index 00000000..1679e3af --- /dev/null +++ b/lib/client/edit/ext-language_tools.js @@ -0,0 +1,1429 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +ace.define('ace/ext/language_tools', ['require', 'exports', 'module' , 'ace/snippets', 'ace/autocomplete', 'ace/config', 'ace/autocomplete/text_completer', 'ace/editor'], function(require, exports, module) { + + +var snippetManager = require("../snippets").snippetManager; +var Autocomplete = require("../autocomplete").Autocomplete; +var config = require("../config"); + +var textCompleter = require("../autocomplete/text_completer"); +var keyWordCompleter = { + getCompletions: function(editor, session, pos, prefix, callback) { + var keywords = session.$mode.$keywordList || []; + keywords = keywords.filter(function(w) { + return w.lastIndexOf(prefix, 0) == 0; + }); + callback(null, keywords.map(function(word) { + return { + name: word, + value: word, + score: 0, + meta: "keyword" + }; + })); + } +}; + +var snippetCompleter = { + getCompletions: function(editor, session, pos, prefix, callback) { + var scope = snippetManager.$getScope(editor); + var snippetMap = snippetManager.snippetMap; + var completions = []; + [scope, "_"].forEach(function(scope) { + var snippets = snippetMap[scope] || []; + for (var i = snippets.length; i--;) { + var s = snippets[i]; + if (s.tabTrigger && s.tabTrigger.indexOf(prefix) === 0) + completions.push({ + caption: s.tabTrigger, + snippet: s.content, + meta: "snippet" + }); + } + }, this); + callback(null, completions); + } +}; + +var completers = [snippetCompleter, textCompleter, keyWordCompleter]; +exports.addCompleter = function(completer) { + completers.push(completer); +}; + +var expandSnippet = { + name: "expandSnippet", + exec: function(editor) { + var success = snippetManager.expandWithTab(editor); + if (!success) + editor.execCommand("indent"); + }, + bindKey: "tab" +} + +var onChangeMode = function(e, editor) { + var mode = editor.session.$mode; + var id = mode.$id + if (!snippetManager.files) snippetManager.files = {}; + if (id && !snippetManager.files[id]) { + var snippetFilePath = id.replace("mode", "snippets"); + config.loadModule(snippetFilePath, function(m) { + if (m) { + snippetManager.files[id] = m; + m.snippets = snippetManager.parseSnippetFile(m.snippetText); + snippetManager.register(m.snippets, m.scope); + } + }); + } +}; + +var Editor = require("../editor").Editor; +require("../config").defineOptions(Editor.prototype, "editor", { + enableBasicAutocompletion: { + set: function(val) { + if (val) { + this.completers = completers + this.commands.addCommand(Autocomplete.startCommand); + } else { + this.commands.removeCommand(Autocomplete.startCommand); + } + }, + value: false + }, + enableSnippets: { + set: function(val) { + if (val) { + this.commands.addCommand(expandSnippet); + this.on("changeMode", onChangeMode); + onChangeMode(null, this) + } else { + this.commands.removeCommand(expandSnippet); + this.off("changeMode", onChangeMode); + } + }, + value: false + } +}); + +}); + +ace.define('ace/snippets', ['require', 'exports', 'module' , 'ace/lib/lang', 'ace/range', 'ace/keyboard/hash_handler', 'ace/tokenizer', 'ace/lib/dom'], function(require, exports, module) { + +var lang = require("./lib/lang") +var Range = require("./range").Range +var HashHandler = require("./keyboard/hash_handler").HashHandler; +var Tokenizer = require("./tokenizer").Tokenizer; +var comparePoints = Range.comparePoints; + +var SnippetManager = function() { + this.snippetMap = {}; + this.snippetNameMap = {}; +}; + +(function() { + this.getTokenizer = function() { + function TabstopToken(str, _, stack) { + str = str.substr(1); + if (/^\d+$/.test(str) && !stack.inFormatString) + return [{tabstopId: parseInt(str, 10)}]; + return [{text: str}] + } + function escape(ch) { + return "(?:[^\\\\" + ch + "]|\\\\.)"; + } + SnippetManager.$tokenizer = new Tokenizer({ + start: [ + {regex: /:/, onMatch: function(val, state, stack) { + if (stack.length && stack[0].expectIf) { + stack[0].expectIf = false; + stack[0].elseBranch = stack[0]; + return [stack[0]]; + } + return ":"; + }}, + {regex: /\\./, onMatch: function(val, state, stack) { + var ch = val[1]; + if (ch == "}" && stack.length) { + val = ch; + }else if ("`$\\".indexOf(ch) != -1) { + val = ch; + } else if (stack.inFormatString) { + if (ch == "n") + val = "\n"; + else if (ch == "t") + val = "\n"; + else if ("ulULE".indexOf(ch) != -1) { + val = {changeCase: ch, local: ch > "a"}; + } + } + + return [val]; + }}, + {regex: /}/, onMatch: function(val, state, stack) { + return [stack.length ? stack.shift() : val]; + }}, + {regex: /\$(?:\d+|\w+)/, onMatch: TabstopToken}, + {regex: /\$\{[\dA-Z_a-z]+/, onMatch: function(str, state, stack) { + var t = TabstopToken(str.substr(1), state, stack); + stack.unshift(t[0]); + return t; + }, next: "snippetVar"}, + {regex: /\n/, token: "newline", merge: false} + ], + snippetVar: [ + {regex: "\\|" + escape("\\|") + "*\\|", onMatch: function(val, state, stack) { + stack[0].choices = val.slice(1, -1).split(","); + }, next: "start"}, + {regex: "/(" + escape("/") + "+)/(?:(" + escape("/") + "*)/)(\\w*):?", + onMatch: function(val, state, stack) { + var ts = stack[0]; + ts.fmtString = val; + + val = this.splitRegex.exec(val); + ts.guard = val[1]; + ts.fmt = val[2]; + ts.flag = val[3]; + return ""; + }, next: "start"}, + {regex: "`" + escape("`") + "*`", onMatch: function(val, state, stack) { + stack[0].code = val.splice(1, -1); + return ""; + }, next: "start"}, + {regex: "\\?", onMatch: function(val, state, stack) { + if (stack[0]) + stack[0].expectIf = true; + }, next: "start"}, + {regex: "([^:}\\\\]|\\\\.)*:?", token: "", next: "start"} + ], + formatString: [ + {regex: "/(" + escape("/") + "+)/", token: "regex"}, + {regex: "", onMatch: function(val, state, stack) { + stack.inFormatString = true; + }, next: "start"} + ] + }); + SnippetManager.prototype.getTokenizer = function() { + return SnippetManager.$tokenizer; + } + return SnippetManager.$tokenizer; + }; + + this.tokenizeTmSnippet = function(str, startState) { + return this.getTokenizer().getLineTokens(str, startState).tokens.map(function(x) { + return x.value || x; + }); + }; + + this.$getDefaultValue = function(editor, name) { + if (/^[A-Z]\d+$/.test(name)) { + var i = name.substr(1); + return (this.variables[name[0] + "__"] || {})[i]; + } + if (/^\d+$/.test(name)) { + return (this.variables.__ || {})[name]; + } + name = name.replace(/^TM_/, ""); + + if (!editor) + return; + var s = editor.session; + switch(name) { + case "CURRENT_WORD": + var r = s.getWordRange(); + case "SELECTION": + case "SELECTED_TEXT": + return s.getTextRange(r); + case "CURRENT_LINE": + return s.getLine(e.getCursorPosition().row); + case "LINE_INDEX": + return e.getCursorPosition().column; + case "LINE_NUMBER": + return e.getCursorPosition().row + 1; + case "SOFT_TABS": + return s.getUseSoftTabs() ? "YES" : "NO"; + case "TAB_SIZE": + return s.getTabSize(); + case "FILENAME": + case "FILEPATH": + return "ace.ajax.org"; + case "FULLNAME": + return "Ace"; + } + }; + this.variables = {}; + this.getVariableValue = function(editor, varName) { + if (this.variables.hasOwnProperty(varName)) + return this.variables[varName](editor, varName) || ""; + return this.$getDefaultValue(editor, varName) || ""; + }; + this.tmStrFormat = function(str, ch, editor) { + var flag = ch.flag || ""; + var re = ch.guard; + re = new RegExp(re, flag.replace(/[^gi]/, "")); + var fmtTokens = this.tokenizeTmSnippet(ch.fmt, "formatString"); + var _self = this; + var formatted = str.replace(re, function() { + _self.variables.__ = arguments; + var fmtParts = _self.resolveVariables(fmtTokens, editor); + var gChangeCase = "E"; + for (var i = 0; i < fmtParts.length; i++) { + var ch = fmtParts[i]; + if (typeof ch == "object") { + fmtParts[i] = ""; + if (ch.changeCase && ch.local) { + var next = fmtParts[i + 1]; + if (next && typeof next == "string") { + if (ch.changeCase == "u") + fmtParts[i] = next[0].toUpperCase(); + else + fmtParts[i] = next[0].toLowerCase(); + fmtParts[i + 1] = next.substr(1); + } + } else if (ch.changeCase) { + gChangeCase = ch.changeCase; + } + } else if (gChangeCase == "U") { + fmtParts[i] = ch.toUpperCase(); + } else if (gChangeCase == "L") { + fmtParts[i] = ch.toLowerCase(); + } + } + return fmtParts.join(""); + }); + this.variables.__ = null; + return formatted; + }; + + this.resolveVariables = function(snippet, editor) { + var result = []; + for (var i = 0; i < snippet.length; i++) { + var ch = snippet[i]; + if (typeof ch == "string") { + result.push(ch); + } else if (typeof ch != "object") { + continue; + } else if (ch.skip) { + gotoNext(ch); + } else if (ch.processed < i) { + continue; + } else if (ch.text) { + var value = this.getVariableValue(editor, ch.text); + if (value && ch.fmtString) + value = this.tmStrFormat(value, ch); + ch.processed = i; + if (ch.expectIf == null) { + if (value) { + result.push(value); + gotoNext(ch); + } + } else { + if (value) { + ch.skip = ch.elseBranch; + } else + gotoNext(ch); + } + } else if (ch.tabstopId != null) { + result.push(ch); + } else if (ch.changeCase != null) { + result.push(ch); + } + } + function gotoNext(ch) { + var i1 = snippet.indexOf(ch, i + 1); + if (i1 != -1) + i = i1; + } + return result; + }; + + this.insertSnippet = function(editor, snippetText) { + var cursor = editor.getCursorPosition(); + var line = editor.session.getLine(cursor.row); + var indentString = line.match(/^\s*/)[0]; + var tabString = editor.session.getTabString(); + + var tokens = this.tokenizeTmSnippet(snippetText); + tokens = this.resolveVariables(tokens, editor); + tokens = tokens.map(function(x) { + if (x == "\n") + return x + indentString; + if (typeof x == "string") + return x.replace(/\t/g, tabString); + return x; + }); + var tabstops = []; + tokens.forEach(function(p, i) { + if (typeof p != "object") + return; + var id = p.tabstopId; + if (!tabstops[id]) { + tabstops[id] = []; + tabstops[id].index = id; + tabstops[id].value = ""; + } + if (tabstops[id].indexOf(p) != -1) + return; + tabstops[id].push(p); + var i1 = tokens.indexOf(p, i + 1); + if (i1 == -1) + return; + var value = tokens.slice(i + 1, i1).join(""); + if (value) + tabstops[id].value = value; + }); + + tabstops.forEach(function(ts) { + ts.value && ts.forEach(function(p) { + var i = tokens.indexOf(p); + var i1 = tokens.indexOf(p, i + 1); + if (i1 == -1) + tokens.splice(i + 1, 0, ts.value, p); + else if (i1 == i + 1) + tokens.splice(i + 1, 0, ts.value); + }); + }); + var row = 0, column = 0; + var text = ""; + tokens.forEach(function(t) { + if (typeof t == "string") { + if (t[0] == "\n"){ + column = t.length - 1; + row ++; + } else + column += t.length; + text += t; + } else { + if (!t.start) + t.start = {row: row, column: column}; + else + t.end = {row: row, column: column}; + } + }); + var range = editor.getSelectionRange(); + var end = editor.session.replace(range, text); + + var tabstopManager = new TabstopManager(editor); + tabstopManager.addTabstops(tabstops, range.start, end); + tabstopManager.tabNext(); + }; + + this.$getScope = function(editor) { + var scope = editor.session.$mode.$id || ""; + scope = scope.split("/").pop(); + if (editor.session.$mode.$modes) { + var c = editor.getCursorPosition() + var state = editor.session.getState(c.row); + if (state.substring) { + if (state.substring(0, 3) == "js-") + scope = "javascript"; + else if (state.substring(0, 4) == "css-") + scope = "css"; + else if (state.substring(0, 4) == "php-") + scope = "php"; + } + } + return scope; + }; + + this.expandWithTab = function(editor) { + var cursor = editor.getCursorPosition(); + var line = editor.session.getLine(cursor.row); + var before = line.substring(0, cursor.column); + var after = line.substr(cursor.column); + + var scope = this.$getScope(editor); + var snippetMap = this.snippetMap; + var snippet; + [scope, "_"].some(function(scope) { + var snippets = snippetMap[scope]; + if (snippets) + snippet = this.findMatchingSnippet(snippets, before, after); + return !!snippet; + }, this); + if (!snippet) + return false; + + editor.session.doc.removeInLine(cursor.row, + cursor.column - snippet.replaceBefore.length, + cursor.column + snippet.replaceAfter.length + ); + + this.variables.M__ = snippet.matchBefore; + this.variables.T__ = snippet.matchAfter; + this.insertSnippet(editor, snippet.content); + + this.variables.M__ = this.variables.T__ = null; + return true; + }; + + this.findMatchingSnippet = function(snippetList, before, after) { + for (var i = snippetList.length; i--;) { + var s = snippetList[i]; + if (s.startRe && !s.startRe.test(before)) + continue; + if (s.endRe && !s.endRe.test(after)) + continue; + if (!s.startRe && !s.endRe) + continue; + + s.matchBefore = s.startRe ? s.startRe.exec(before) : [""]; + s.matchAfter = s.endRe ? s.endRe.exec(after) : [""]; + s.replaceBefore = s.triggerRe ? s.triggerRe.exec(before)[0] : ""; + s.replaceAfter = s.endTriggerRe ? s.endTriggerRe.exec(after)[0] : ""; + return s; + } + }; + + this.snippetMap = {}; + this.snippetNameMap = {}; + this.register = function(snippets, scope) { + var snippetMap = this.snippetMap; + var snippetNameMap = this.snippetNameMap; + var self = this; + function wrapRegexp(src) { + if (src && !/^\^?\(.*\)\$?$|^\\b$/.test(src)) + src = "(?:" + src + ")" + + return src || ""; + } + function guardedRegexp(re, guard, opening) { + re = wrapRegexp(re); + guard = wrapRegexp(guard); + if (opening) { + re = guard + re; + if (re && re[re.length - 1] != "$") + re = re + "$"; + } else { + re = re + guard; + if (re && re[0] != "^") + re = "^" + re; + } + return new RegExp(re); + } + + function addSnippet(s) { + if (!s.scope) + s.scope = scope || "_"; + scope = s.scope + if (!snippetMap[scope]) { + snippetMap[scope] = []; + snippetNameMap[scope] = {}; + } + + var map = snippetNameMap[scope]; + if (s.name) { + var old = map[s.name]; + if (old) + self.unregister(old); + map[s.name] = s; + } + snippetMap[scope].push(s); + + if (s.tabTrigger && !s.trigger) { + if (!s.guard && /^\w/.test(s.tabTrigger)) + s.guard = "\\b"; + s.trigger = lang.escapeRegExp(s.tabTrigger); + } + + s.startRe = guardedRegexp(s.trigger, s.guard, true); + s.triggerRe = new RegExp(s.trigger, "", true); + + s.endRe = guardedRegexp(s.endTrigger, s.endGuard, true); + s.endTriggerRe = new RegExp(s.endTrigger, "", true); + }; + + if (snippets.content) + addSnippet(snippets); + else if (Array.isArray(snippets)) + snippets.forEach(addSnippet); + }; + this.unregister = function(snippets, scope) { + var snippetMap = this.snippetMap; + var snippetNameMap = this.snippetNameMap; + + function removeSnippet(s) { + var nameMap = snippetNameMap[s.scope||scope]; + if (nameMap && nameMap[s.name]) { + delete nameMap[s.name]; + var map = snippetMap[s.scope||scope]; + var i = map && map.indexOf(s); + if (i >= 0) + map.splice(i, 1); + } + } + if (snippets.content) + removeSnippet(snippets); + else if (Array.isArray(snippets)) + snippets.forEach(removeSnippet); + }; + this.parseSnippetFile = function(str) { + str = str.replace(/\r/, ""); + var list = [], snippet = {}; + var re = /^#.*|^({[\s\S]*})\s*$|^(\S+) (.*)$|^((?:\n*\t.*)+)/gm; + var m; + while (m = re.exec(str)) { + if (m[1]) { + try { + snippet = JSON.parse(m[1]) + list.push(snippet); + } catch (e) {} + } if (m[4]) { + snippet.content = m[4].replace(/^\t/gm, ""); + list.push(snippet); + snippet = {}; + } else { + var key = m[2], val = m[3]; + if (key == "regex") { + var guardRe = /\/((?:[^\/\\]|\\.)*)|$/g; + snippet.guard = guardRe.exec(val)[1]; + snippet.trigger = guardRe.exec(val)[1]; + snippet.endTrigger = guardRe.exec(val)[1]; + snippet.endGuard = guardRe.exec(val)[1]; + } else if (key == "snippet") { + snippet.tabTrigger = val.match(/^\S*/)[0]; + if (!snippet.name) + snippet.name = val; + } else { + snippet[key] = val; + } + } + } + return list; + }; + this.getSnippetByName = function(name, editor) { + var scope = editor && this.$getScope(editor); + var snippetMap = this.snippetNameMap; + var snippet; + [scope, "_"].some(function(scope) { + var snippets = snippetMap[scope]; + if (snippets) + snippet = snippets[name]; + return !!snippet; + }, this); + return snippet; + }; + +}).call(SnippetManager.prototype); + + +var TabstopManager = function(editor) { + if (editor.tabstopManager) + return editor.tabstopManager; + editor.tabstopManager = this; + this.$onChange = this.onChange.bind(this); + this.$onChangeSelection = lang.delayedCall(this.onChangeSelection.bind(this)).schedule; + this.$onChangeSession = this.onChangeSession.bind(this); + this.$onAfterExec = this.onAfterExec.bind(this); + this.attach(editor); +}; +(function() { + this.attach = function(editor) { + this.index = -1; + this.ranges = []; + this.tabstops = []; + this.selectedTabstop = null; + + this.editor = editor; + this.editor.on("change", this.$onChange); + this.editor.on("changeSelection", this.$onChangeSelection); + this.editor.on("changeSession", this.$onChangeSession); + this.editor.commands.on("afterExec", this.$onAfterExec); + this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler); + }; + this.detach = function() { + this.tabstops.forEach(this.removeTabstopMarkers, this); + this.ranges = null; + this.tabstops = null; + this.selectedTabstop = null; + this.editor.removeListener("change", this.$onChange); + this.editor.removeListener("changeSelection", this.$onChangeSelection); + this.editor.removeListener("changeSession", this.$onChangeSession); + this.editor.commands.removeListener("afterExec", this.$onAfterExec); + this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler); + this.editor.tabstopManager = null; + this.editor = null; + }; + + this.onChange = function(e) { + var changeRange = e.data.range; + var isRemove = e.data.action[0] == "r"; + var start = changeRange.start; + var end = changeRange.end; + var startRow = start.row; + var endRow = end.row; + var lineDif = endRow - startRow; + var colDiff = end.column - start.column; + + if (isRemove) { + lineDif = -lineDif; + colDiff = -colDiff; + } + if (!this.$inChange && isRemove) { + var ts = this.selectedTabstop; + var changedOutside = !ts.some(function(r) { + return comparePoints(r.start, start) <= 0 && comparePoints(r.end, end) >= 0; + }); + if (changedOutside) + return this.detach(); + } + var ranges = this.ranges; + for (var i = 0; i < ranges.length; i++) { + var r = ranges[i]; + if (r.end.row < start.row) + continue; + + if (comparePoints(start, r.start) < 0 && comparePoints(end, r.end) > 0) { + this.removeRange(r); + i--; + continue; + } + + if (r.start.row == startRow && r.start.column > start.column) + r.start.column += colDiff; + if (r.end.row == startRow && r.end.column >= start.column) + r.end.column += colDiff; + if (r.start.row >= startRow) + r.start.row += lineDif; + if (r.end.row >= startRow) + r.end.row += lineDif; + + if (comparePoints(r.start, r.end) > 0) + this.removeRange(r); + } + if (!ranges.length) + this.detach(); + }; + this.updateLinkedFields = function() { + var ts = this.selectedTabstop; + if (!ts.hasLinkedRanges) + return; + this.$inChange = true; + var session = this.editor.session; + var text = session.getTextRange(ts.firstNonLinked); + for (var i = ts.length; i--;) { + var range = ts[i]; + if (!range.linked) + continue; + var fmt = exports.snippetManager.tmStrFormat(text, range.original) + session.replace(range, fmt); + } + this.$inChange = false; + }; + this.onAfterExec = function(e) { + if (e.command && !e.command.readOnly) + this.updateLinkedFields(); + }; + this.onChangeSelection = function() { + if (!this.editor) + return + var lead = this.editor.selection.lead; + var anchor = this.editor.selection.anchor; + var isEmpty = this.editor.selection.isEmpty(); + for (var i = this.ranges.length; i--;) { + if (this.ranges[i].linked) + continue; + var containsLead = this.ranges[i].contains(lead.row, lead.column); + var containsAnchor = isEmpty || this.ranges[i].contains(anchor.row, anchor.column); + if (containsLead && containsAnchor) + return; + } + this.detach(); + }; + this.onChangeSession = function() { + this.detach(); + }; + this.tabNext = function(dir) { + var max = this.tabstops.length - 1; + var index = this.index + (dir || 1); + index = Math.min(Math.max(index, 0), max); + this.selectTabstop(index); + if (index == max) + this.detach(); + }; + this.selectTabstop = function(index) { + var ts = this.tabstops[this.index]; + if (ts) + this.addTabstopMarkers(ts); + this.index = index; + ts = this.tabstops[this.index]; + if (!ts || !ts.length) + return; + + this.selectedTabstop = ts; + if (!this.editor.inVirtualSelectionMode) { + var sel = this.editor.multiSelect; + sel.toSingleRange(ts.firstNonLinked.clone()); + for (var i = ts.length; i--;) { + if (ts.hasLinkedRanges && ts[i].linked) + continue; + sel.addRange(ts[i].clone(), true); + } + } else { + this.editor.selection.setRange(ts.firstNonLinked); + } + + this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler); + }; + this.addTabstops = function(tabstops, start, end) { + if (!tabstops[0]) { + var p = Range.fromPoints(end, end); + moveRelative(p.start, start); + moveRelative(p.end, start); + tabstops[0] = [p]; + tabstops[0].index = 0; + } + + var i = this.index; + var arg = [i, 0]; + var ranges = this.ranges; + var editor = this.editor; + tabstops.forEach(function(ts) { + for (var i = ts.length; i--;) { + var p = ts[i]; + var range = Range.fromPoints(p.start, p.end || p.start); + movePoint(range.start, start); + movePoint(range.end, start); + range.original = p; + range.tabstop = ts; + ranges.push(range); + ts[i] = range; + if (p.fmtString) { + range.linked = true; + ts.hasLinkedRanges = true; + } else if (!ts.firstNonLinked) + ts.firstNonLinked = range; + } + if (!ts.firstNonLinked) + ts.hasLinkedRanges = false; + arg.push(ts); + this.addTabstopMarkers(ts); + }, this); + arg.push(arg.splice(2, 1)[0]); + this.tabstops.splice.apply(this.tabstops, arg); + }; + + this.addTabstopMarkers = function(ts) { + var session = this.editor.session; + ts.forEach(function(range) { + if (!range.markerId) + range.markerId = session.addMarker(range, "ace_snippet-marker", "text"); + }); + }; + this.removeTabstopMarkers = function(ts) { + var session = this.editor.session; + ts.forEach(function(range) { + session.removeMarker(range.markerId); + range.markerId = null; + }); + }; + this.removeRange = function(range) { + var i = range.tabstop.indexOf(range); + range.tabstop.splice(i, 1); + i = this.ranges.indexOf(range); + this.ranges.splice(i, 1); + this.editor.session.removeMarker(range.markerId); + }; + + this.keyboardHandler = new HashHandler(); + this.keyboardHandler.bindKeys({ + "Tab": function(ed) { + ed.tabstopManager.tabNext(1); + }, + "Shift-Tab": function(ed) { + ed.tabstopManager.tabNext(-1); + }, + "Esc": function(ed) { + ed.tabstopManager.detach(); + }, + "Return": function(ed) { + return false; + } + }); +}).call(TabstopManager.prototype); + + +var movePoint = function(point, diff) { + if (point.row == 0) + point.column += diff.column; + point.row += diff.row; +}; + +var moveRelative = function(point, start) { + if (point.row == start.row) + point.column -= start.column; + point.row -= start.row; +}; + + +require("./lib/dom").importCssString("\ +.ace_snippet-marker {\ + -moz-box-sizing: border-box;\ + box-sizing: border-box;\ + background: rgba(194, 193, 208, 0.09);\ + border: 1px dotted rgba(211, 208, 235, 0.62);\ + position: absolute;\ +}"); + +exports.snippetManager = new SnippetManager(); + + +}); + +ace.define('ace/autocomplete', ['require', 'exports', 'module' , 'ace/keyboard/hash_handler', 'ace/autocomplete/popup', 'ace/autocomplete/util', 'ace/lib/event', 'ace/lib/lang', 'ace/snippets'], function(require, exports, module) { + + +var HashHandler = require("./keyboard/hash_handler").HashHandler; +var AcePopup = require("./autocomplete/popup").AcePopup; +var util = require("./autocomplete/util"); +var event = require("./lib/event"); +var lang = require("./lib/lang"); +var snippetManager = require("./snippets").snippetManager; + +var Autocomplete = function() { + this.keyboardHandler = new HashHandler(); + this.keyboardHandler.bindKeys(this.commands); + + this.blurListener = this.blurListener.bind(this); + this.changeListener = this.changeListener.bind(this); + this.mousedownListener = this.mousedownListener.bind(this); + this.mousewheelListener = this.mousewheelListener.bind(this); + + this.changeTimer = lang.delayedCall(function() { + this.updateCompletions(true); + }.bind(this)) +}; + +(function() { + this.$init = function() { + this.popup = new AcePopup(document.body || document.documentElement); + this.popup.on("click", function(e) { + this.insertMatch(); + e.stop(); + }.bind(this)); + }; + + this.openPopup = function(editor, keepPopupPosition) { + if (!this.popup) + this.$init(); + + this.popup.setData(this.completions.filtered); + + var renderer = editor.renderer; + if (!keepPopupPosition) { + var lineHeight = renderer.layerConfig.lineHeight; + var pos = renderer.$cursorLayer.getPixelPosition(null, true); + var rect = editor.container.getBoundingClientRect(); + pos.top += rect.top - renderer.layerConfig.offset; + pos.left += rect.left; + pos.left += renderer.$gutterLayer.gutterWidth; + + this.popup.show(pos, lineHeight); + } + renderer.updateText(); + }; + + this.detach = function() { + this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler); + this.editor.removeEventListener("changeSelection", this.changeListener); + this.editor.removeEventListener("blur", this.changeListener); + this.editor.removeEventListener("mousedown", this.changeListener); + this.changeTimer.cancel(); + + if (this.popup) + this.popup.hide(); + + this.activated = false; + }; + + this.changeListener = function(e) { + if (this.activated) + this.changeTimer.schedule(); + else + this.detach(); + }; + + this.blurListener = function() { + if (document.activeElement != this.editor.textInput.getElement()) + this.detach(); + }; + + this.mousedownListener = function(e) { + this.detach(); + }; + + this.mousewheelListener = function(e) { + this.detach(); + }; + + this.goTo = function(where) { + var row = this.popup.getRow(); + var max = this.popup.session.getLength() - 1; + + switch(where) { + case "up": row = row <= 0 ? max : row - 1; break; + case "down": row = row >= max ? 0 : row + 1; break; + case "start": row = 0; break; + case "end": row = max; break; + } + + this.popup.setRow(row); + }; + + this.insertMatch = function(data) { + this.detach(); + if (!data) + data = this.popup.getData(this.popup.getRow()); + if (!data) + return false; + if (data.completer && data.completer.insertMatch) { + data.completer.insertMatch(this.editor); + } else { + if (this.completions.filterText) { + var range = this.editor.selection.getRange(); + range.start.column -= this.completions.filterText.length; + this.editor.session.remove(range); + } + if (data.snippet) + snippetManager.insertSnippet(this.editor, data.snippet); + else + this.editor.insert(data.value || data); + } + }; + + this.commands = { + "Up": function(editor) { editor.completer.goTo("up"); }, + "Down": function(editor) { editor.completer.goTo("down"); }, + "Ctrl-Up|Ctrl-Home": function(editor) { editor.completer.goTo("start"); }, + "Ctrl-Down|Ctrl-End": function(editor) { editor.completer.goTo("end"); }, + + "Esc": function(editor) { editor.completer.detach(); }, + "Space": function(editor) { editor.completer.detach(); editor.insert(" ");}, + "Return": function(editor) { editor.completer.insertMatch(); }, + "Shift-Return": function(editor) { editor.completer.insertMatch(true); }, + "Tab": function(editor) { editor.completer.insertMatch(); }, + + "PageUp": function(editor) { editor.completer.popup.gotoPageDown(); }, + "PageDown": function(editor) { editor.completer.popup.gotoPageUp(); } + }; + + this.gatherCompletions = function(editor, callback) { + var session = editor.getSession(); + var pos = editor.getCursorPosition(); + + var line = session.getLine(pos.row); + var prefix = util.retrievePrecedingIdentifier(line, pos.column); + + var matches = []; + util.parForEach(editor.completers, function(completer, next) { + completer.getCompletions(editor, session, pos, prefix, function(err, results) { + if (!err) + matches = matches.concat(results); + next(); + }); + }, function() { + matches.sort(function(a, b) { + return b.score - a.score; + }); + callback(null, { + prefix: prefix, + matches: matches + }); + }); + return true; + }; + + this.showPopup = function(editor) { + if (this.editor) + this.detach(); + + this.activated = true; + + this.editor = editor; + if (editor.completer != this) { + if (editor.completer) + editor.completer.detach(); + editor.completer = this; + } + + editor.keyBinding.addKeyboardHandler(this.keyboardHandler); + editor.on("changeSelection", this.changeListener); + editor.on("blur", this.blurListener); + editor.on("mousedown", this.mousedownListener); + this.updateCompletions(); + } + + this.updateCompletions = function(keepPopupPosition) { + this.gatherCompletions(this.editor, function(err, results) { + var matches = results && results.matches; + if (!matches || !matches.length) + return this.detach(); + + this.completions = new FilteredList(matches); + this.completions.setFilter(results.prefix); + this.openPopup(this.editor, keepPopupPosition); + this.popup.setHighlight(results.prefix); + }.bind(this)); + }; + + this.cancelContextMenu = function() { + var stop = function(e) { + this.editor.off("nativecontextmenu", stop); + if (e && e.domEvent) + event.stopEvent(e.domEvent); + }.bind(this); + setTimeout(stop, 10); + this.editor.on("nativecontextmenu", stop); + }; + +}).call(Autocomplete.prototype); + +Autocomplete.startCommand = { + name: "startAutocomplete", + exec: function(editor) { + if (!editor.completer) + editor.completer = new Autocomplete(); + editor.completer.showPopup(editor); + editor.completer.cancelContextMenu(); + }, + bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space" +}; + +var FilteredList = function(array, mutateData) { + this.all = array; + this.filtered = array.concat(); + this.filterText = ""; +}; +(function(){ + this.setFilter = function(str) { + this.filterText = str; + }; + +}).call(FilteredList.prototype); + +exports.Autocomplete = Autocomplete; +exports.FilteredList = FilteredList; + +}); + +ace.define('ace/autocomplete/popup', ['require', 'exports', 'module' , 'ace/edit_session', 'ace/virtual_renderer', 'ace/editor', 'ace/range', 'ace/lib/event', 'ace/lib/lang', 'ace/lib/dom'], function(require, exports, module) { + + +var EditSession = require("../edit_session").EditSession; +var Renderer = require("../virtual_renderer").VirtualRenderer; +var Editor = require("../editor").Editor; +var Range = require("../range").Range; +var event = require("../lib/event"); +var lang = require("../lib/lang"); +var dom = require("../lib/dom"); + +var $singleLineEditor = function(el) { + var renderer = new Renderer(el); + + renderer.$maxLines = 4; + + var editor = new Editor(renderer); + + editor.setHighlightActiveLine(false); + editor.setShowPrintMargin(false); + editor.renderer.setShowGutter(false); + editor.renderer.setHighlightGutterLine(false); + + editor.$mouseHandler.$focusWaitTimout = 0; + + return editor; +}; + +var AcePopup = function(parentNode) { + var el = dom.createElement("div"); + var popup = new $singleLineEditor(el); + + if (parentNode) + parentNode.appendChild(el); + el.style.display = "none"; + popup.renderer.content.style.cursor = "default"; + popup.renderer.setStyle("ace_autocomplete"); + + var noop = function(){}; + + popup.focus = noop; + popup.$isFocused = true; + + popup.renderer.$cursorLayer.restartTimer = noop; + popup.renderer.$cursorLayer.element.style.opacity = 0; + + popup.renderer.$maxLines = 8; + popup.renderer.$keepTextAreaAtCursor = false; + + popup.setHighlightActiveLine(true); + popup.session.highlight(""); + popup.session.$searchHighlight.clazz = "ace_highlight-marker"; + + popup.on("mousedown", function(e) { + var pos = e.getDocumentPosition(); + popup.moveCursorToPosition(pos); + popup.selection.clearSelection(); + e.stop(); + }); + + var hoverMarker = new Range(-1,0,-1,Infinity); + hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine"); + popup.on("mousemove", function(e) { + var row = e.getDocumentPosition().row; + hoverMarker.start.row = hoverMarker.end.row = row; + popup.session._emit("changeBackMarker"); + }); + var hideHoverMarker = function() { + hoverMarker.start.row = hoverMarker.end.row = -1; + popup.session._emit("changeBackMarker"); + }; + event.addListener(popup.container, "mouseout", hideHoverMarker); + popup.on("hide", hideHoverMarker); + popup.on("changeSelection", hideHoverMarker); + popup.on("mousewheel", function(e) { + setTimeout(function() { + popup._signal("mousemove", e); + }); + }); + + popup.session.doc.getLength = function() { + return popup.data.length; + }; + popup.session.doc.getLine = function(i) { + var data = popup.data[i]; + if (typeof data == "string") + return data; + return (data && data.value) || ""; + }; + + var bgTokenizer = popup.session.bgTokenizer; + bgTokenizer.$tokenizeRow = function(i) { + var data = popup.data[i]; + var tokens = []; + if (!data) + return tokens; + if (typeof data == "string") + data = {value: data}; + if (!data.caption) + data.caption = data.value; + + tokens.push({type: data.className || "", value: data.caption}); + if (data.meta) { + var maxW = popup.renderer.$size.scrollerWidth / popup.renderer.layerConfig.characterWidth; + if (data.meta.length + data.caption.length < maxW - 2) + tokens.push({type: "rightAlignedText", value: data.meta}); + } + return tokens; + }; + bgTokenizer.$updateOnChange = noop; + + popup.session.$computeWidth = function() { + return this.screenWidth = 0; + } + popup.data = []; + popup.setData = function(list) { + popup.data = list || []; + popup.setValue(lang.stringRepeat("\n", list.length), -1); + }; + popup.getData = function(row) { + return popup.data[row]; + }; + + popup.getRow = function() { + var line = this.getCursorPosition().row; + if (line == 0 && !this.getHighlightActiveLine()) + line = -1; + return line; + }; + popup.setRow = function(line) { + popup.setHighlightActiveLine(line != -1); + popup.selection.clearSelection(); + popup.moveCursorTo(line, 0 || 0); + }; + + popup.setHighlight = function(re) { + popup.session.highlight(re); + popup.session._emit("changeFrontMarker"); + }; + + popup.hide = function() { + this.container.style.display = "none"; + this._signal("hide"); + }; + popup.show = function(pos, lineHeight) { + var el = this.container; + if (pos.top > window.innerHeight / 2 + lineHeight) { + el.style.top = ""; + el.style.bottom = window.innerHeight - pos.top + "px"; + } else { + pos.top += lineHeight; + el.style.top = pos.top + "px"; + el.style.bottom = ""; + } + + el.style.left = pos.left + "px"; + el.style.display = ""; + this.renderer.$textLayer.checkForSizeChanges(); + + this._signal("show"); + }; + + return popup; +}; + +dom.importCssString("\ +.ace_autocomplete.ace-tm .ace_marker-layer .ace_active-line {\ + background-color: #abbffe;\ +}\ +.ace_autocomplete.ace-tm .ace_line-hover {\ + border: 1px solid #abbffe;\ + position: absolute;\ + background: rgba(233,233,253,0.4);\ + z-index: 2;\ + margin-top: -1px;\ +}\ +.ace_rightAlignedText {\ + color: gray;\ + display: inline-block;\ + position: absolute;\ + right: 4px;\ + text-align: right;\ + z-index: -1;\ +}\ +.ace_autocomplete {\ + width: 200px;\ + z-index: 200000;\ + background: #f8f8f8;\ + border: 1px lightgray solid;\ + position: fixed;\ + box-shadow: 2px 3px 5px rgba(0,0,0,.2);\ +}"); + +exports.AcePopup = AcePopup; + +}); + +ace.define('ace/autocomplete/util', ['require', 'exports', 'module' ], function(require, exports, module) { + + +exports.parForEach = function(array, fn, callback) { + var completed = 0; + var arLength = array.length; + if (arLength === 0) + callback(); + for (var i = 0; i < arLength; i++) { + fn(array[i], function(result, err) { + completed++; + if (completed === arLength) + callback(result, err); + }); + } +} + +var ID_REGEX = /[a-zA-Z_0-9\$-]/; + +exports.retrievePrecedingIdentifier = function(text, pos, regex) { + regex = regex || ID_REGEX; + var buf = []; + for (var i = pos-1; i >= 0; i--) { + if (regex.test(text[i])) + buf.push(text[i]); + else + break; + } + return buf.reverse().join(""); +} + +exports.retrieveFollowingIdentifier = function(text, pos, regex) { + regex = regex || ID_REGEX; + var buf = []; + for (var i = pos; i < text.length; i++) { + if (regex.test(text[i])) + buf.push(text[i]); + else + break; + } + return buf; +} + +}); + +ace.define('ace/autocomplete/text_completer', ['require', 'exports', 'module' , 'ace/range'], function(require, exports, module) { + var Range = require("ace/range").Range; + + var splitRegex = /[^a-zA-Z_0-9\$\-]+/; + + function getWordIndex(doc, pos) { + var textBefore = doc.getTextRange(Range.fromPoints({row: 0, column:0}, pos)); + return textBefore.split(splitRegex).length - 1; + } + function filterPrefix(prefix, words) { + var results = []; + for (var i = 0; i < words.length; i++) { + if (words[i].lastIndexOf(prefix, 0) === 0) { + results.push(words[i]); + } + } + return results; + } + function wordDistance(doc, pos) { + var prefixPos = getWordIndex(doc, pos); + var words = doc.getValue().split(splitRegex); + var wordScores = Object.create(null); + + var currentWord = words[prefixPos]; + + words.forEach(function(word, idx) { + if (!word || word === currentWord) return; + + var distance = Math.abs(prefixPos - idx); + var score = words.length - distance; + if (wordScores[word]) { + wordScores[word] = Math.max(score, wordScores[word]); + } else { + wordScores[word] = score; + } + }); + return wordScores; + } + + exports.getCompletions = function(editor, session, pos, prefix, callback) { + var wordScore = wordDistance(session, pos, prefix); + var wordList = filterPrefix(prefix, Object.keys(wordScore)); + callback(null, wordList.map(function(word) { + return { + name: word, + value: word, + score: wordScore[word], + meta: "local" + }; + })); + }; +}); \ No newline at end of file diff --git a/lib/client/edit/snippets/javascript.js b/lib/client/edit/snippets/javascript.js new file mode 100644 index 00000000..c8845fbe --- /dev/null +++ b/lib/client/edit/snippets/javascript.js @@ -0,0 +1,202 @@ +ace.define('ace/snippets/javascript', ['require', 'exports', 'module' ], function(require, exports, module) { + + +exports.snippetText = "# Prototype\n\ +snippet proto\n\ + ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n\ + ${4:// body...}\n\ + };\n\ +# Function\n\ +snippet fun\n\ + function ${1?:function_name}(${2:argument}) {\n\ + ${3:// body...}\n\ + }\n\ +# Anonymous Function\n\ +regex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\n\ +name f\n\ + function${M1?: ${1:functionName}}($2) {\n\ + ${0:$TM_SELECTED_TEXT}\n\ + }${M2?;}${M3?,}${M4?)}\n\ +# Immediate function\n\ +trigger \\(?f\\(\n\ +endTrigger \\)?\n\ +snippet f(\n\ + (function(${1}) {\n\ + ${0:${TM_SELECTED_TEXT:/* code */}}\n\ + }(${1}));\n\ +# if\n\ +snippet if\n\ + if (${1:true}) {\n\ + ${0}\n\ + }\n\ +# if ... else\n\ +snippet ife\n\ + if (${1:true}) {\n\ + ${2}\n\ + } else {\n\ + ${0}\n\ + }\n\ +# tertiary conditional\n\ +snippet ter\n\ + ${1:/* condition */} ? ${2:a} : ${3:b}\n\ +# switch\n\ +snippet switch\n\ + switch (${1:expression}) {\n\ + case '${3:case}':\n\ + ${4:// code}\n\ + break;\n\ + ${5}\n\ + default:\n\ + ${2:// code}\n\ + }\n\ +# case\n\ +snippet case\n\ + case '${1:case}':\n\ + ${2:// code}\n\ + break;\n\ + ${3}\n\ +\n\ +# while (...) {...}\n\ +snippet wh\n\ + while (${1:/* condition */}) {\n\ + ${0:/* code */}\n\ + }\n\ +# try\n\ +snippet try\n\ + try {\n\ + ${0:/* code */}\n\ + } catch (e) {}\n\ +# do...while\n\ +snippet do\n\ + do {\n\ + ${2:/* code */}\n\ + } while (${1:/* condition */});\n\ +# Object Method\n\ +snippet :f\n\ +regex /([,{[])|^\\s*/:f/\n\ + ${1:method_name}: function(${2:attribute}) {\n\ + ${0}\n\ + }${3:,}\n\ +# setTimeout function\n\ +snippet setTimeout\n\ +regex /\\b/st|timeout|setTimeo?u?t?/\n\ + setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n\ +# Get Elements\n\ +snippet gett\n\ + getElementsBy${1:TagName}('${2}')${3}\n\ +# Get Element\n\ +snippet get\n\ + getElementBy${1:Id}('${2}')${3}\n\ +# console.log (Firebug)\n\ +snippet cl\n\ + console.log(${1});\n\ +# return\n\ +snippet ret\n\ + return ${1:result}\n\ +# for (property in object ) { ... }\n\ +snippet fori\n\ + for (var ${1:prop} in ${2:Things}) {\n\ + ${0:$2[$1]}\n\ + }\n\ +# hasOwnProperty\n\ +snippet has\n\ + hasOwnProperty(${1})\n\ +# docstring\n\ +snippet /**\n\ + /**\n\ + * ${1:description}\n\ + *\n\ + */\n\ +snippet @par\n\ +regex /^\\s*\\*\\s*/@(para?m?)?/\n\ + @param {${1:type}} ${2:name} ${3:description}\n\ +snippet @ret\n\ + @return {${1:type}} ${2:description}\n\ +# JSON.parse\n\ +snippet jsonp\n\ + JSON.parse(${1:jstr});\n\ +# JSON.stringify\n\ +snippet jsons\n\ + JSON.stringify(${1:object});\n\ +# self-defining function\n\ +snippet sdf\n\ + var ${1:function_name} = function(${2:argument}) {\n\ + ${3:// initial code ...}\n\ +\n\ + $1 = function($2) {\n\ + ${4:// main code}\n\ + };\n\ + }\n\ +# singleton\n\ +snippet sing\n\ + function ${1:Singleton} (${2:argument}) {\n\ + // the cached instance\n\ + var instance;\n\ +\n\ + // rewrite the constructor\n\ + $1 = function $1($2) {\n\ + return instance;\n\ + };\n\ + \n\ + // carry over the prototype properties\n\ + $1.prototype = this;\n\ +\n\ + // the instance\n\ + instance = new $1();\n\ +\n\ + // reset the constructor pointer\n\ + instance.constructor = $1;\n\ +\n\ + ${3:// code ...}\n\ +\n\ + return instance;\n\ + }\n\ +# class\n\ +name class\n\ +regex /^\\s*/clas{0,2}/\n\ + var ${1:class} = function(${20}) {\n\ + $40$0\n\ + };\n\ + \n\ + (function() {\n\ + ${60:this.prop = \"\"}\n\ + }).call(${1:class}.prototype);\n\ + \n\ + exports.${1:class} = ${1:class};\n\ +# \n\ +snippet for-\n\ + for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n\ + ${0:${2:Things}[${1:i}];}\n\ + }\n\ +# for (...) {...}\n\ +snippet for\n\ + for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n\ + ${3:$2[$1]}$0\n\ + }\n\ +# for (...) {...} (Improved Native For-Loop)\n\ +snippet forr\n\ + for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n\ + ${3:$2[$1]}$0\n\ + }\n\ +\n\ +\n\ +#modules\n\ +snippet def\n\ + ace.define(function(require, exports, module) {\n\ + \"use strict\";\n\ + var ${1/.*\\///} = require(\"${1}\");\n\ + \n\ + $TM_SELECTED_TEXT\n\ + });\n\ +snippet req\n\ +guard ^\\s*\n\ + var ${1/.*\\///} = require(\"${1}\");\n\ + $0\n\ +snippet requ\n\ +guard ^\\s*\n\ + var ${1/.*\\/(.)/\\u$1/} = require(\"${1}\").${1/.*\\/(.)/\\u$1/};\n\ + $0\n\ +"; +exports.scope = "javascript"; + +});