/* ***** 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" }; })); }; });