diff --git a/Makefile.dryice.js b/Makefile.dryice.js index 6c0f2bea..650527a5 100755 --- a/Makefile.dryice.js +++ b/Makefile.dryice.js @@ -232,7 +232,7 @@ function buildAce(aceProject, options) { "tomorrow_night_blue", "tomorrow_night_bright", "tomorrow_night_eighties", "twilight", "vibrant_ink" ], - workers: ["javascript", "coffee", "css"], + workers: ["javascript", "coffee", "css", "json"], keybindings: ["vim", "emacs"] }; diff --git a/lib/ace/mode/coffee_worker.js b/lib/ace/mode/coffee_worker.js index 74cc1f63..58150903 100644 --- a/lib/ace/mode/coffee_worker.js +++ b/lib/ace/mode/coffee_worker.js @@ -34,10 +34,10 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ - + define(function(require, exports, module) { "use strict"; - + var oop = require("../lib/oop"); var Mirror = require("../worker/mirror").Mirror; var coffee = require("../mode/coffee/coffee-script"); @@ -53,29 +53,29 @@ var Worker = exports.Worker = function(sender) { oop.inherits(Worker, Mirror); (function() { - + this.onUpdate = function() { var value = this.doc.getValue(); - + try { coffee.parse(value); } catch(e) { var m = e.message.match(/Parse error on line (\d+): (.*)/); if (m) { this.sender.emit("error", { - row: parseInt(m[1]) - 1, + row: parseInt(m[1], 10) - 1, column: null, text: m[2], type: "error" }); return; } - + if (e instanceof SyntaxError) { var m = e.message.match(/ on line (\d+)/); - if (m) { + if (m) { this.sender.emit("error", { - row: parseInt(m[1]) - 1, + row: parseInt(m[1], 10) - 1, column: null, text: e.message.replace(m[0], ""), type: "error" @@ -86,7 +86,7 @@ oop.inherits(Worker, Mirror); } this.sender.emit("ok"); }; - + }).call(Worker.prototype); }); \ No newline at end of file diff --git a/lib/ace/mode/json.js b/lib/ace/mode/json.js index 72449565..fc0b9583 100644 --- a/lib/ace/mode/json.js +++ b/lib/ace/mode/json.js @@ -45,6 +45,7 @@ var HighlightRules = require("./json_highlight_rules").JsonHighlightRules; var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent; var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour; var CStyleFoldMode = require("./folding/cstyle").FoldMode; +var WorkerClient = require("../worker/worker_client").WorkerClient; var Mode = function() { this.$tokenizer = new Tokenizer(new HighlightRules().getRules()); @@ -76,7 +77,23 @@ oop.inherits(Mode, TextMode); this.autoOutdent = function(state, doc, row) { this.$outdent.autoOutdent(doc, row); }; - + + this.createWorker = function(session) { + var worker = new WorkerClient(["ace"], "worker-json.js", "ace/mode/json_worker", "JsonWorker"); + worker.attachToDocument(session.getDocument()); + + worker.on("error", function(e) { + session.setAnnotations([e.data]); + }); + + worker.on("ok", function() { + session.clearAnnotations(); + }); + + return worker; + }; + + }).call(Mode.prototype); exports.Mode = Mode; diff --git a/lib/ace/mode/json/json_parse.js b/lib/ace/mode/json/json_parse.js new file mode 100644 index 00000000..8374d2af --- /dev/null +++ b/lib/ace/mode/json/json_parse.js @@ -0,0 +1,346 @@ +/* + http://www.JSON.org/json_parse.js + 2008-09-18 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + This file creates a json_parse function. + + json_parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = json_parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +/*members "", "\"", "\/", "\\", at, b, call, charAt, f, fromCharCode, + hasOwnProperty, message, n, name, push, r, t, text +*/ + +define(function(require, exports, module) { +"use strict"; + +// This is a function that can parse a JSON text, producing a JavaScript +// data structure. It is a simple, recursive descent parser. It does not use +// eval or regular expressions, so it can be used as a model for implementing +// a JSON parser in other languages. + +// We are defining the function inside of another function to avoid creating +// global variables. + + var at, // The index of the current character + ch, // The current character + escapee = { + '"': '"', + '\\': '\\', + '/': '/', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t' + }, + text, + + error = function (m) { + +// Call error when something is wrong. + + throw { + name: 'SyntaxError', + message: m, + at: at, + text: text + }; + }, + + next = function (c) { + +// If a c parameter is provided, verify that it matches the current character. + + if (c && c !== ch) { + error("Expected '" + c + "' instead of '" + ch + "'"); + } + +// Get the next character. When there are no more characters, +// return the empty string. + + ch = text.charAt(at); + at += 1; + return ch; + }, + + number = function () { + +// Parse a number value. + + var number, + string = ''; + + if (ch === '-') { + string = '-'; + next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { + string += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + } + number = +string; + if (isNaN(number)) { + error("Bad number"); + } else { + return number; + } + }, + + string = function () { + +// Parse a string value. + + var hex, + i, + string = '', + uffff; + +// When parsing for string values, we must look for " and \ characters. + + if (ch === '"') { + while (next()) { + if (ch === '"') { + next(); + return string; + } else if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else { + string += ch; + } + } + } + error("Bad string"); + }, + + white = function () { + +// Skip whitespace. + + while (ch && ch <= ' ') { + next(); + } + }, + + word = function () { + +// true, false, or null. + + switch (ch) { + case 't': + next('t'); + next('r'); + next('u'); + next('e'); + return true; + case 'f': + next('f'); + next('a'); + next('l'); + next('s'); + next('e'); + return false; + case 'n': + next('n'); + next('u'); + next('l'); + next('l'); + return null; + } + error("Unexpected '" + ch + "'"); + }, + + value, // Place holder for the value function. + + array = function () { + +// Parse an array value. + + var array = []; + + if (ch === '[') { + next('['); + white(); + if (ch === ']') { + next(']'); + return array; // empty array + } + while (ch) { + array.push(value()); + white(); + if (ch === ']') { + next(']'); + return array; + } + next(','); + white(); + } + } + error("Bad array"); + }, + + object = function () { + +// Parse an object value. + + var key, + object = {}; + + if (ch === '{') { + next('{'); + white(); + if (ch === '}') { + next('}'); + return object; // empty object + } + while (ch) { + key = string(); + white(); + next(':'); + if (Object.hasOwnProperty.call(object, key)) { + error('Duplicate key "' + key + '"'); + } + object[key] = value(); + white(); + if (ch === '}') { + next('}'); + return object; + } + next(','); + white(); + } + } + error("Bad object"); + }; + + value = function () { + +// Parse a JSON value. It could be an object, an array, a string, a number, +// or a word. + + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && ch <= '9' ? number() : word(); + } + }; + +// Return the json_parse function. It will have access to all of the above +// functions and variables. + + return function (source, reviver) { + var result; + + text = source; + at = 0; + ch = ' '; + result = value(); + white(); + if (ch) { + error("Syntax error"); + } + +// If there is a reviver function, we recursively walk the new structure, +// passing each name/value pair to the reviver function for possible +// transformation, starting with a temporary root object that holds the result +// in an empty key. If there is not a reviver function, we simply return the +// result. + + return typeof reviver === 'function' ? function walk(holder, key) { + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + }({'': result}, '') : result; + }; +}); \ No newline at end of file diff --git a/lib/ace/mode/json_worker.js b/lib/ace/mode/json_worker.js new file mode 100644 index 00000000..8759c0b9 --- /dev/null +++ b/lib/ace/mode/json_worker.js @@ -0,0 +1,103 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; + +var oop = require("../lib/oop"); +var Mirror = require("../worker/mirror").Mirror; +var parse = require("./json/json_parse"); + +var JsonWorker = exports.JsonWorker = function(sender) { + Mirror.call(this, sender); + this.setTimeout(200); +}; + +oop.inherits(JsonWorker, Mirror); + +(function() { + + this.onUpdate = function() { + var value = this.doc.getValue(); + + try { + var result = parse(value); + } catch (e) { + var pos = this.charToDocumentPosition(e.at-1); + this.sender.emit("error", { + row: pos.row, + column: pos.column, + text: e.message, + type: "error" + }); + return; + } + this.sender.emit("ok"); + }; + + this.charToDocumentPosition = function(charPos) { + var i = 0; + var len = this.doc.getLength(); + var nl = this.doc.getNewLineCharacter().length; + + if (!len) { + return { row: 0, column: 0}; + } + + var lineStart = 0; + while (i < len) { + var line = this.doc.getLine(i); + var lineLength = line.length + nl; + if (lineStart + lineLength > charPos) + return { + row: i, + column: charPos - lineStart + }; + + lineStart += lineLength; + i += 1; + } + + return { + row: i-1, + column: line.length + }; + }; + +}).call(JsonWorker.prototype); + +}); \ No newline at end of file diff --git a/lib/ace/mode/json_worker_test.js b/lib/ace/mode/json_worker_test.js new file mode 100644 index 00000000..261fc9c5 --- /dev/null +++ b/lib/ace/mode/json_worker_test.js @@ -0,0 +1,108 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +if (typeof process !== "undefined") { + require("amd-loader"); +} + +define(function(require, exports, module) { +"use strict"; + +var assert = require("../test/assertions"); +var Worker = require("./json_worker").JsonWorker; + + +module.exports = { + setUp : function() { + this.sender = { + on: function() {}, + callback: function(data, id) { + this.data = data; + }, + events: [], + emit: function(type, e) { + this.events.push([type, e]); + } + }; + }, + + "test check valid json": function() { + var worker = new Worker(this.sender); + worker.setValue("{}"); + worker.deferredUpdate.call(); + + assert.equal(this.sender.events[0][0], "ok"); + }, + + "test check for syntax error": function() { + var worker = new Worker(this.sender); + worker.setValue([ + "{", + "juhu: 12", + "}" + ].join("\n")); + worker.deferredUpdate.call(); + + var event = this.sender.events[0]; + assert.equal(event[0], "error"); + assert.equal(event[1].type, "error"); + assert.equal(event[1].text, "Bad string"); + assert.equal(event[1].row, 1); + assert.equal(event[1].column, 0); + + }, + + "test check for syntax error at first char": function() { + var worker = new Worker(this.sender); + worker.setValue("x"); + worker.deferredUpdate.call(); + + var event = this.sender.events[0]; + assert.equal(event[0], "error"); + assert.equal(event[1].type, "error"); + assert.equal(event[1].text, "Unexpected 'x'"); + assert.equal(event[1].row, 0); + assert.equal(event[1].column, 0); + } + +}; + +}); + +if (typeof module !== "undefined" && module === require.main) { + require("asyncjs").test.testcase(module.exports).exec(); +} \ No newline at end of file diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index 31cc9a5d..8948cb83 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -104,7 +104,7 @@ var WorkerClient = function(topLevelNamespaces, packagedJs, mod, classname) { oop.implement(this, EventEmitter); this.$normalizePath = function(path) { - path = path.replace(/^[a-z]+:\/\/[^\/]+\//, ""); // Remove domain name and rebuild it + path = path.replace(/^[a-z]+:\/\/[^\/]+/, ""); // Remove domain name and rebuild it path = location.protocol + "//" + location.host // paths starting with a slash are relative to the root (host) + (path.charAt(0) == "/" ? "" : location.pathname.replace(/\/[^\/]*$/, ""))