diff --git a/demo/kitchen-sink/util.js b/demo/kitchen-sink/util.js index 9ba4545a..761f6a59 100644 --- a/demo/kitchen-sink/util.js +++ b/demo/kitchen-sink/util.js @@ -151,6 +151,12 @@ exports.saveOption = function(el, val) { } exports.bindCheckbox = function(id, callback, noInit) { + if (typeof id == "string") + var el = document.getElementById(id); + else { + var el = id; + id = el.id; + } var el = document.getElementById(id); if (localStorage && localStorage.getItem(id)) el.checked = localStorage.getItem(id) == "1"; @@ -164,7 +170,12 @@ exports.bindCheckbox = function(id, callback, noInit) { } exports.bindDropdown = function(id, callback, noInit) { - var el = document.getElementById(id); + if (typeof id == "string") + var el = document.getElementById(id); + else { + var el = id; + id = el.id; + } if (localStorage && localStorage.getItem(id)) el.value = localStorage.getItem(id); @@ -190,7 +201,7 @@ function elt(tag, attributes, content) { var el = dom.createElement(tag); if (typeof content == "string") { el.textContent = content; - } else { + } else if (content) { content.forEach(function(ch) { el.appendChild(ch); }); @@ -203,6 +214,8 @@ function elt(tag, attributes, content) { function optgroup(values) { return values.map(function(item) { + if (typeof item == "string") + item = {name: item, desc: item}; return elt("option", {value: item.name}, item.desc); }); } diff --git a/lib/ace/tokenizer_dev.js b/lib/ace/tokenizer_dev.js new file mode 100644 index 00000000..1972890f --- /dev/null +++ b/lib/ace/tokenizer_dev.js @@ -0,0 +1,205 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, 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 ***** */ + +define(function(require, exports, module) { + +/** + * version of Tokenizer with additional logging + * and infinite loop checks + * can be used for developing/testing new modes + **/ +var Tokenizer = function(rules, flag) { + flag = flag ? "g" + flag : "g"; + this.rules = rules; + + this.regExps = {}; + this.matchMappings = {}; + for ( var key in this.rules) { + var rule = this.rules[key]; + var state = rule; + var ruleRegExps = []; + var matchTotal = 0; + var mapping = this.matchMappings[key] = {}; + + for ( var i = 0; i < state.length; i++) { + + if (state[i].regex instanceof RegExp) + state[i].regex = state[i].regex.toString().slice(1, -1); + + // Count number of matching groups. 2 extra groups from the full match + // And the catch-all on the end (used to force a match); + var matchcount = new RegExp("(?:(" + state[i].regex + ")|(.))").exec("a").length - 2; + + // Replace any backreferences and offset appropriately. + var adjustedregex = state[i].regex.replace(/\\([0-9]+)/g, function (match, digit) { + return "\\" + (parseInt(digit, 10) + matchTotal + 1); + }); + + if (matchcount > 1 && state[i].token.length !== matchcount-1) + throw new Error("For " + state[i].regex + " the matching groups and length of the token array don't match (rule #" + i + " of state " + key + ")"); + + mapping[matchTotal] = { + rule: i, + len: matchcount + }; + matchTotal += matchcount; + + ruleRegExps.push(adjustedregex); + } + + this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", flag); + } +}; + +(function() { + this.getLineTokens = function(line, startState) { + var currentState = startState || "start"; + var state = this.rules[currentState]; + var mapping = this.matchMappings[currentState]; + var re = this.regExps[currentState]; + re.lastIndex = 0; + + var match, tokens = []; + + var lastIndex = 0; + + var stateTransitions = []; + function onStateChange() { + stateTransitions.push(startState+"@"+lastIndex); + } + function initState() { + onStateChange(); + stateTransitions = []; + onStateChange(); + } + + var token = { + type: null, + value: "", + state: currentState, + }; + initState(); + + var maxRecur = 10000; + + while (match = re.exec(line)) { + var type = "default.text"; + var rule = null; + var value = [match[0]]; + + for (var i = 0; i < match.length-2; i++) { + if (match[i + 1] === undefined) + continue; + + if (!maxRecur--) { + throw "infinite" + mapping[i].rule + currentState + } + + rule = state[mapping[i].rule]; + + if (mapping[i].len > 1) + value = match.slice(i+2, i+1+mapping[i].len); + + // compute token type + if (typeof rule.token == "function") + type = rule.token.apply(this, value); + else + type = rule.token; + + if (rule.next) { + currentState = rule.next; + state = this.rules[currentState]; + mapping = this.matchMappings[currentState]; + lastIndex = re.lastIndex; + + re = this.regExps[currentState]; + + if (re === undefined) { + throw new Error("You indicated a state of " + rule.next + " to go to, but it doesn't exist!"); + } + + re.lastIndex = lastIndex; + + onStateChange(); + } + break; + } + + if (value[0]) { + if (typeof type == "string") { + value = [value.join("")]; + type = [type]; + } + for (var i = 0; i < value.length; i++) { + if (!value[i]) + continue; + + var mergeable = (!rule || rule.merge || type[i] === "text") && token.type === type[i]; + + if (false && mergeable) { + token.value += value[i]; + } else { + if (token.type) { + token.stateTransitions = stateTransitions; + tokens.push(token); + initState() + } + + token = { + type: type[i], + value: value[i], + state: currentState, + mergeable: mergeable + }; + } + } + } + + if (lastIndex == line.length) + break; + + lastIndex = re.lastIndex; + } + + if (token.type) { + token.stateTransitions = stateTransitions; + tokens.push(token); + } + + return { + tokens : tokens, + state : currentState + }; + }; + +}).call(Tokenizer.prototype); + +exports.Tokenizer = Tokenizer; +}); diff --git a/tool/mode_creator.html b/tool/mode_creator.html new file mode 100644 index 00000000..e45acd73 --- /dev/null +++ b/tool/mode_creator.html @@ -0,0 +1,64 @@ + + + + + + + Ace Mode Creator + + + + + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/tool/mode_creator.js b/tool/mode_creator.js new file mode 100644 index 00000000..a927cd04 --- /dev/null +++ b/tool/mode_creator.js @@ -0,0 +1,138 @@ +define(function(require, exports, module) { + +/** creates globals intentionally to make things easily accessible from console **/ + +var net = require("ace/lib/net"); +var Range = require("ace/range").Range; +var util = require("demo/kitchen-sink/util"); +var layout = require("demo/kitchen-sink/layout"); +var modelist = require("demo/kitchen-sink/modelist"); +var doclist = require("demo/kitchen-sink/doclist"); +var TokenTooltip = require("demo/kitchen-sink/token_tooltip").TokenTooltip; + +var EditSession = require("ace/edit_session").EditSession; +var UndoManager = require("ace/undomanager").UndoManager; + +var DebugTokenizer = require("ace/tokenizer_dev").Tokenizer; + +// createEditor +var splitEditor = window.splitEditor = util.createSplitEditor("editor"); + +var editor1 = window.editor1 = splitEditor.editor0; +var editor2 = window.editor2 = splitEditor.editor1; +new TokenTooltip(editor2); + +timeout = null +schedule = function() { + if(timeout != null) { + clearTimeout(timeout) + } + timeout = setTimeout(run, 800) +} + + +setAutorunEnabled = function(val) { + if (val) + editor1.on('change', schedule) + else + editor1.removeEventListener('change', schedule) +} + +util.bindCheckbox("autorunEl", setAutorunEnabled) + + +docEl = document.getElementById("doc") +util.fillDropdown(docEl, doclist.docs) +util.bindDropdown("doc", function(value) { + doclist.loadDoc(value, function(session) { + if (session) { + editor2.setSession(session) + } + }) +}); + +modeEl = document.getElementById("modeEl"); +util.fillDropdown(modeEl, modelist.modes) +var modeSessions = {} +util.bindDropdown(modeEl, function(value) { + if (modeSessions[value]) + editor1.setSession(modeSessions[value]) + var hp = "./lib/ace/mode/" + value + "_highlight_rules.js" + net.get(hp, function(text) { + text = util.stripLeadingComments(text) + var desc = value + modePath = "ace/mode/" + value + + var session = new EditSession(text) + session.setUndoManager(new UndoManager()) + modeSessions[value] = session; + session.setMode("ace/mode/javascript") + + editor1.setSession(modeSessions[value]) + }) +}); + +util.fillDropdown("themeEl", { + bright: [ + "chrome", "clouds", "crimson_editor", "dawn", "dreamweaver", "eclipse", "github", + "solarized_light", "textmate", "tomorrow", "xcode"], + dark: [ "clouds_midnight", "cobalt", "idle_fingers", "kr_theme", "merbivore", "merbivore_soft", + "mono_industrial", "monokai", "pastel_on_dark", "solarized_dark", "tomorrow_night", + "tomorrow_night_blue", "tomorrow_night_bright", "tomorrow_night_eighties", "twilight", "vibrant_ink"] +}) + +util.bindDropdown("themeEl", function(value) { + if (!value) + return; + editor1.setTheme("ace/theme/" + value); + editor2.setTheme("ace/theme/" + value); +}) + + +function getDeps(src, path) { + var deps = [] + src.replace(/require\((['"])(.*?)\1/g, function(a,b,c){ + if (c[0] == ".") { + var base = path.split("/") + c.split("/").forEach(function(part) { + if (part == ".") { + base.pop() + } else if (part == "..") { + base.pop(); + base.pop() + } else { + base.push(part) + } + }) + c = base.join("/") + } + deps.push('"' + c + '"') + }) + + return deps +} +function run() { + var src = editor1.getValue(); + var path = "ace/mode/new"; + var deps = getDeps(src, path) + src = src.replace("define(", 'define("' + path +'", ["require","exports","module",' + deps +'],'); + src += ';require(["ace/mode/new"], continueRun, function(e){console.log(e);require.undef("ace/mode/new")})' + try { + eval(src) + } catch(e) { + console.log(e) + } +} +var continueRun = function(rules) { + rules = rules[Object.keys(rules)[0]] + var Tokenizer = DebugTokenizer + + var tk = new Tokenizer(new rules().getRules()) + editor2.session.bgTokenizer.setTokenizer(tk) + editor2.renderer.updateText() +} + +editor1.commands.bindKey("ctrl-Return", run) + +}); + diff --git a/tool/mode_tool.html b/tool/mode_tool.html deleted file mode 100644 index 58b67446..00000000 --- a/tool/mode_tool.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - Editor - - - - -
-

-
-
-
-
-
-
-
-