add simple page for live editing highlighters

This commit is contained in:
nightwing 2012-09-30 01:34:12 +04:00
commit 854f21595b
5 changed files with 422 additions and 186 deletions

View file

@ -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);
});
}

205
lib/ace/tokenizer_dev.js Normal file
View file

@ -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;
});

64
tool/mode_creator.html Normal file
View file

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="../">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Ace Mode Creator</title>
<meta name="author" content="Harutyun Amirjanyan">
<style type="text/css" >
body, html {
overflow: hidden;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
#header {
border-bottom: solid 1px;
}
.separator-h {
padding: 0 20px;
}
</style>
</head>
<body>
<div id="header">
<label for="modeEl">mode</label>
<select id="modeEl" size="1"></select>
<span id="tablist"></span>
<span class="separator-h"></span>
<label for="autorunEl">live preview</label>
<input type="checkbox" label="autorun" id="autorunEl" checked>
<div style='float:right'>
<label for="themeEl">Theme</label>
<select id="themeEl" size="1" value="textmate"></select>
<span class="separator-h"></span>
<label for="doc">Document</label>
<select id="doc" size="1"></select>
</div>
</div>
<div id="editor"></div>
<script type="text/javascript">
var require = {
baseUrl: window.location.protocol + "//" + window.location.host + window.location.pathname.split("/").slice(0, -1).join("/"),
paths: {
ace: "../lib/ace",
demo: "../demo",
tool: "../tool"
}
};
</script>
<script src="./demo/kitchen-sink/require.js" data-main="mode_creator"></script>
</body>
</html>

138
tool/mode_creator.js Normal file
View file

@ -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)
});

View file

@ -1,184 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Editor</title>
<style type="text/css" media="screen">
body {
overflow: hidden;
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#editor {
}
</style>
</head>
<body>
<div id="toolbar"></div>
<pre id="editor"></pre>
<script src="../build/src/ace.js" type="text/javascript"></script>
<script src="../build/kitchen-sink/demo.js" type="text/javascript"></script>
<script>
var $ = document.getElementById.bind(document);
function bindDropdown(el, callback) {
var onChange = function() {
callback(el.value);
};
el.onchange = onChange;
onChange();
}
function fillDropdown(list, el) {
list.forEach(function(item) {
var option = document.createElement("option");
option.setAttribute("value", item.name);
option.innerHTML = item.desc;
el.appendChild(option);
});
}
/************** modes ***********************/
var modes = [];
function getModeFromPath(path) {
var mode = modesByName.text;
for (var i = 0; i < modes.length; i++) {
if (modes[i].supportsFile(path)) {
mode = modes[i];
break;
}
}
return mode;
};
var Mode = function(name, desc, extensions) {
this.name = name;
this.desc = desc;
this.mode = "ace/mode/" + name;
this.extRe = new RegExp("^.*\\.(" + extensions + ")$", "g");
};
Mode.prototype.supportsFile = function(filename) {
return filename.match(this.extRe);
};
var modesByName = {
c9search: ["C9Search" , "c9search_results"],
coffee: ["CoffeeScript" , "coffee|^Cakefile"],
coldfusion: ["ColdFusion" , "cfm"],
csharp: ["C#" , "cs"],
css: ["CSS" , "css"],
diff: ["Diff" , "diff|patch"],
golang: ["Go" , "go"],
groovy: ["Groovy" , "groovy"],
haxe: ["haXe" , "hx"],
html: ["HTML" , "htm|html|xhtml"],
c_cpp: ["C/C++" , "c|cc|cpp|cxx|h|hh|hpp"],
clojure: ["Clojure" , "clj"],
java: ["Java" , "java"],
javascript: ["JavaScript" , "js"],
json: ["JSON" , "json"],
jsx: ["JSX" , "jsx"],
latex: ["LaTeX" , "latex|tex|ltx|bib"],
less: ["LESS" , "less"],
liquid: ["Liquid" , "liquid"],
lua: ["Lua" , "lua"],
luapage: ["LuaPage" , "lp"], // http://keplerproject.github.com/cgilua/manual.html#templates
markdown: ["Markdown" , "md|markdown"],
ocaml: ["OCaml" , "ml|mli"],
perl: ["Perl" , "pl|pm"],
pgsql: ["pgSQL" , "pgsql"],
php: ["PHP" , "php|phtml"],
powershell: ["Powershell" , "ps1"],
python: ["Python" , "py"],
ruby: ["Ruby" , "ru|gemspec|rake|rb"],
scad: ["OpenSCAD" , "scad"],
scala: ["Scala" , "scala"],
scss: ["SCSS" , "scss|sass"],
sh: ["SH" , "sh|bash|bat"],
sql: ["SQL" , "sql"],
svg: ["SVG" , "svg"],
text: ["Text" , "txt"],
textile: ["Textile" , "textile"],
xml: ["XML" , "xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl"],
xquery: ["XQuery" , "xq"],
yaml: ["YAML" , "yaml"]
};
for (var name in modesByName) {
var mode = modesByName[name];
mode = new Mode(name, mode[0], mode[1])
modesByName[name] = mode;
modes.push(mode);
}
var container = document.getElementById("editor");
container.style.position = "absolute";
// Splitting.
var Split = require("ace/split").Split;
var split = new Split(container, null, 1);
split.setOrientation(split.BESIDE);
split.setSplits(2);
var editor1 = split.getEditor(0);
var editor2 = split.getEditor(1);
var toolbar = $("toolbar");
var modeEl = document.createElement("select");
fillDropdown(modes, modeEl);
bindDropdown(modeEl, function(value) {
editor1.session.setMode((modesByName[value] || modesByName.text).mode);
});
toolbar.appendChild(modeEl);
var button = document.createElement("input");
button.setAttribute("type", "button");
button.value = "generate test";
toolbar.appendChild(button)
function onResize() {
var top = toolbar.clientHeight;
var width = document.documentElement.clientWidth;
container.style.top = top + "px";
container.style.width = width + "px";
container.style.height = document.documentElement.clientHeight - top + "px";
split.resize();
}
window.onresize = onResize;
onResize();
button.onclick = function() {
var s = editor1.session
tok = s.bgTokenizer
var data = []
for (var i = 0; i < s.getLength(); i++) {
data.push({
text: s.getLine(i),
state: [tok.getState(i - 1), tok.getState(i)],
tokens: tok.getTokens(i)
})
}
data = JSON.stringify(data, null, 4);
data = data.replace(/(\n\s*)"(\w+)":/g, "$1$2:");
editor2.insert(data);
}
</script>
</body>
</html>