[
  {
    "path": ".gitignore",
    "content": "/config/config.local.js\r\n/node_modules/\r\n/nbproject/\r\ncache.json\r\nsamples_output\r\n.idea\r\natlassian-ide-plugin.xml\r\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2011 by Aleksander Kmetec\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "Aardwolf\n========\n\nAardwolf is a remote JavaScript debugger for Android / iOS / Windows Phone 7 / BlackBerry OS 6+ and is written in JavaScript. It's available under the MIT license.\n\nHome page: http://lexandera.com/aardwolf/\n\nCurrently it supports:\n\n* breakpoints\n* code evaluation at breakpoint\n* break on next\n* step/continue execution control\n* stack listing\n* exception reporting (also for exceptions thrown in async calls)\n* JavaScript console remoting\n\n\nIt consists of the following parts:\n\n* a server for communication between the mobile device and the UI\n* a code rewriter which injects debug info into your existing source code\n* a debug library which can break execution of your scripts, report execution progress, evaluate code, etc.\n* a UI for setting breakpoints, stepping through code and seeing the current position within the script\n\n\nIn order to run the examples you will need:\n\n* Node.js. Get it here: http://nodejs.org/#download\n* An Android 2.x/iOS/WindowsPhone7 device or emulator (although running them from a Firefox/Chrome/Safari window will also work)\n\n\nSetting it up\n----------------------------------------------------------------------------------------------------\n\n* Begin by installing node.js and Git\n* Get the Aardwolf source code from GitHub: \n`git clone git://github.com/lexandera/Aardwolf.git`\n* Download the required libraries by running \"npm link\" in the checked-out directory\n* Start the server by running \"node app.js -h &lt;ip-or-hostname-of-your-computer&gt;\"\n* After the server starts up, open http://localhost:8000 in your desktop browser. The debugger UI should appear.\n* Open http://ip-or-hostname-of-your-computer:8500/calc.html on your phone and wait for the page to load. The line \"Mobile device connected.\" should appear in the UI's output pane.\n* You're now debugging the \"calculator\" example script.\n\n\nIf you're having problems opening the example, make sure that access to the port 8500 on your computer is not blocked by a firewall and that the address you entered into the config file can really be accessed from your phone. This is where your phone will load the samples from, so it must work.\n\nYou will get best results by connecting both you computer and your phone to the same WiFi network.\n\n\nCoffeeScript support\n----------------------------------------------------------------------------------------------------\n\nAardwolf now also contains extrememly basic CoffeeScript support. It probably can't handle any serious real-world code, but it's a good starting point if someone wishes to fork the source and work on it.\n\nThe steps for debugging the CoffeeScript example are the same as the steps described above, except:\n\n* Replace calc.html with calc-coffee.html in the final step when opening the example.\n\n\nDebugging your own code\n----------------------------------------------------------------------------------------------------\n\nThe procedure is the same as above, except:\n\n* When starting the server, add an additional parameter called -d or --file-dir, like this:  \n    `node app.js -h <ip-or-hostname-of-your-computer> -d </path/to/www/root>`\n* In your HTML page include the aardwolf.js debug library as the very first JS file and change the paths of included files to point to the files modified by Aardwolf:\n    <pre>\n    &lt;script type=\"text/javascript\" src=\"http://ip-or-hostname-of-your-computer:8500/aardwolf.js\"&gt; &lt;/script&gt;\n    &lt;script type=\"text/javascript\" src=\"http://ip-or-hostname-of-your-computer:8500/some-script.js\"&gt; &lt;/script&gt;\n    &lt;script type=\"text/javascript\" src=\"http://ip-or-hostname-of-your-computer:8500/some-other-script.js\"&gt; &lt;/script&gt;\n    </pre>\n* Reload the debugger UI first, then reload the page you just modified. The line \"Mobile device connected.\" should appear in the UI's output pane.\n* You should now be able to evaluate code remotely, set breakpoints, etc.\n\n\nDebugging processed or minified code\n----------------------------------------------------------------------------------------------------\n\nIf you wish to debug code which gets concatenated into a single file, minified, or transformed in some other way, you can still use Aardwolf, but you'll need to make a minor change in the part of your application which reads the code before it gets transformed.\n\nIt is important that Aardwolf can access source files before they are processed. Therefore you will need to set it up just as described in the previous section, with the '-d' parameter pointing to the directory containing unprocessed files, then change the processing code in you application so it reads files served by Aardwolf instead of reading them straight from the filesystem.\n\nFor example, if your code looks something like this:\n\n    jscode += readFile('some-script.js');\n    jscode += readFile('some-other-script.js');\n\nyou would need to change it to something like this:\n    \n    jscode += readFile('http://aardwolf-host:8500/aardwolf.js'); // Don't forget to include this!\n    jscode += readFile('http://aardwolf-host:8500/some-script.js');\n    jscode += readFile('http://aardwolf-host:8500/some-other-script.js');\n\nIn most languages, making the modification should be pretty straightforward. PHP's `file_get_contents($url)` and Clojure's `(slurp url)` will handle the change from local paths to URLs transparently. In Scala you can use `io.Source.fromURL(url).mkString`, Ruby has the 'OpenURI' module and in NodeJS you should be able to read remote files using the 'request' module.\n\nNow you should be ready to debug processed code. And since Aardwolf has access to the original files, its UI will display the original, unprocessed code for easier debugging.\n\n\nHow it works\n----------------------------------------------------------------------------------------------------\n\nBreaking code execution and evaluating code at that point is enabled by code rewriting. Aardwolf's server contains a rather simple code rewriter which inserts debug hooks in front of every statement in the source code. These debug statements look like this:\n\n    Aardwolf.updatePosition(  \n        \"/calc.js\", // File path  \n        7,          // Line number  \n        false,      // Is current line a \"debugger;\" statement?  \n        function(aardwolfEval) {       // This closure captures the current scope and makes it  \n            return eval(aardwolfEval); // possible to pass it into another function.  \n        }  \n    );  \n\nThe first two parameters – file path and line number – should be self explanatory. Every time `Aardwolf.updatePosition()` is called, the given file and line number are checked against a list of breakpoints, and if a match is found, script execution is halted by performing a synchronous XMLHttpRequest to the server.\n\nThe third parameter signals whether the current line contains a `debugger;` statement. If it does, we must break execution even if there is no breakpoint set on that line.\n\nFinally, the last parameter is a closure which captures the scope it's defined in and allows us to pass it around. When a string is passed to this function for evaluation, it will be eval'd in the same scope where this closure was defined, thus enabling us to evaluate code at the point where script execution was halted."
  },
  {
    "path": "app.js",
    "content": "'use strict';\n\nvar argv = require('optimist').argv;\nvar fs = require('fs');\nvar config = require('./config/config.defaults.js');\n\nif (argv['h'])         { config.serverHost        = argv['h']; }\nif (argv['host'])      { config.serverHost        = argv['host']; }\n\nif (argv['p'])         { config.serverPort        = argv['p']; }\nif (argv['port'])      { config.serverPort        = argv['port']; }\n\nif (argv['d'])         { config.fileServerBaseDir = argv['d']; }\nif (argv['file-dir'])  { config.fileServerBaseDir = argv['file-dir']; }\n\nif (argv['file-port']) { config.fileServerPort    = argv['file-port']; }\n\nif (argv['o'])\t\t   { config.outputDir = argv['o']; }\n\nif (argv['v'])\t\t   { config.verbose = true; }\n\nif (argv['white-list']){ config.whiteList = argv['white-list'].split(','); }\n\ntry {\n    /* Makes sure the path exists and gets rid of any trailing slashes. */\n    config.fileServerBaseDir = fs.realpathSync(config.fileServerBaseDir);\n} catch (e) {\n    console.error(e.message);\n    process.exit(1);\n}\n\nArray.prototype.equals = function (otherArray) {\n\treturn !(this < otherArray || otherArray < this);\n}\n\nvar ips = scanAvailableIPs();\n\nif (config.serverHost && ips.indexOf(config.serverHost) < 0) {\n\tconsole.error('Configured host', config.serverHost, 'is not valid. You don\\'t have that IP');\n\tconsole.error('Available IPs are:', ips);\n\tprocess.exit(1);\n}\n\nif (!config.serverHost) {\n\n\tif (ips.length > 1) {\n\t\tconsole.log(\"Cannot decide which IP to use, please specify one of these\");\n\t\tips.forEach(function(ip, i) {\n\t\t\tconsole.log('[', i + 1, ']:', ip);\n\t\t})\n\n\t\tvar prompt = require('prompt');\n\t\tprompt.start();\n\t\tprompt.get({name: 'selection', validator: /^\\d{1}$/, empty: false}, function(err, result) {\n\t\t\tvar ip = ips[result.selection - 1];\n\t\t\tconsole.log('Choosen option', ip);\n\t\t\tconfig.serverHost = ip;\n\t\t\tstartApp();\n\t\t})\n\t} else {\n\t\tconfig.serverHost = ips[0];\n\t\tstartApp();\n\t}\n} else {\n\tstartApp();\n}\n\nfunction scanAvailableIPs() {\n\tvar os = require('os');\n\tvar interfaces = os.networkInterfaces(),\n\t\tips = [];\n\tfor (var dev in interfaces) {\n\t\tinterfaces[dev].forEach(function(details){\n\t\t\tif (details.family == 'IPv4' && details.address != '127.0.0.1') {\n\t\t\t\tips.push(details.address);\n\t\t\t}\n\t\t});\n\t}\n\treturn ips;\n}\n\nfunction startApp() {\n\tvar server = require('./server/server.js');\n\tserver.run();\n\n\tif (config.runDebugServer) {\n\t\tvar debugFileServer = require('./server/debug-file-server.js');\n\t\tdebugFileServer.run();\n\t}\n\n\tif (config.runOfflineRewriter) {\n\t\tvar rewriterServer = require('./server/rewriter-server.js');\n\t\trewriterServer.run();\n\t}\n}\n\n\n\n\n\n"
  },
  {
    "path": "config/config.defaults.js",
    "content": "'use strict';\n\n/*\n *\n * To change defaults set in this file create a file called config.local.js\n * in the same directory and override values like this:\n *\n *     var config = require('../config/config.defaults.js');\n *     config.setting = 'new_value';\n *\n */\n\nvar config = {};\nvar path = require('path');\nvar fs = require('fs');\n\n\n/* Verbose mode */\nconfig.verbose = false;\n\n/* Run the debug file server or not */\nconfig.runDebugServer = true;\n\n/* Run the offline rewriter process or not */\nconfig.runOfflineRewriter = false;\n\n/* Hostname or IP of the local machine */\nconfig.serverHost = ''; // Can be retrieved automatically or asked to the user\n\n/* port on which the server listens for requests */\nconfig.serverPort = 8000;\n\n/* Full path to directory holding source files you wish to debug */\nconfig.fileServerBaseDir = path.join(__dirname, '../samples');\n\n/* Port on which files will be served */\nconfig.fileServerPort = 8500;\n\n/* Output folder in which to put debugging-enabled files */\nconfig.outputDir = path.join(__dirname, '../samples_output');\n\n/* Files which won't be copied to the output folder */\nconfig.ignoreFiles = ['.git', '.svn'];\n\n/* Files which won't be processed by the debugger */\nconfig.blackList = [];\n\n/* Force this files as the only ones to be processed by the debugger */\nconfig.whiteList = [];\n\n/* Index file of the application */\nconfig.indexFile = '/index.html';\n\n/* After which tag insert aardwolf script in the modified index file */\nconfig.whereToInsertAardwolf = '<head>';\n\n/* Aardwolf script tag */\nconfig.aardwolfScript = '<script type=\"text/javascript\" src=\"aardwolf.js\"></script>';\n\nmodule.exports = config;\n\n/* Load overrides from config.local.js if it exists */\nvar localConf = path.join(__dirname, 'config.local.js');\nif (fs.existsSync(localConf)) {\n    require(localConf);\n}\n"
  },
  {
    "path": "js/aardwolf.js",
    "content": "/*\n * Aardwold mobile runtime library.\n *\n * Do not enable JS strict mode for this file as it\n * will disable some functionality this library depends on.\n */\nwindow.Aardwolf = new (function() {\n    var serverHost = '__SERVER_HOST__';\n    var serverPort = '__SERVER_PORT__';\n    var serverUrl = 'http://' + serverHost + ':' + serverPort;\n    var breakpoints = {};\n    var shouldBreak = function() { return false; };\n    var asyncXHR = null;\n    var lastFile = '';\n    var lastLine = '';\n\n\tvar listenTimeout = null;\n\n\tvar consoleRedirected = false;\n\n    function listenToServer() {\n        try {\n            dropCommandConnection();\n\n            asyncXHR = new XMLHttpRequest();\n            asyncXHR.open('GET', serverUrl + '/mobile/incoming', true);\n            asyncXHR.onreadystatechange = function () {\n                if (asyncXHR.readyState == 4) {\n                    if (asyncXHR.responseText) {\n                        var cmd = safeJSONParse(asyncXHR.responseText);\n\n                        if (cmd && cmd.command == 'eval') {\n                            doEval(function(aardwolfEval) { return eval(aardwolfEval); }, cmd);\n                        }\n                        else {\n                            processCommand(cmd);\n                        }\n\t\t\t\t\t\tsetTimeout(listenToServer, 0);\n                    }\n                }\n            };\n\t\t\tlistenTimeout = setTimeout(function() {\n\t\t\t\tdropCommandConnection();\n\t\t\t\tlistenToServer();\n\t\t\t}, 50 * 1000); // Internal XHR timeout fires at 60 secs, we let an adecuate margin to restart the server.\n\n            asyncXHR.send(null);\n        } catch (ex) {\n            alert('Aardwolf encountered an error while waiting for command: ' + ex.toString());\n            listenToServer();\n        }\n    }\n\n    function dropCommandConnection() {\n        if (asyncXHR) {\n            asyncXHR.abort();\n        }\n\t\tif (listenTimeout) {\n\t\t\tclearTimeout(listenTimeout);\n\t\t}\n        asyncXHR = null;\n\t\tlistenTimeout = null;\n    }\n\n    function sendToServer(path, payload) {\n        try {\n            var req = new XMLHttpRequest();\n            req.open('POST', serverUrl + '/mobile' + path, false);\n            req.setRequestHeader('Content-Type', 'application/json');\n\t\t\tif (path === '/breakpoint') {\n\t\t\t\treq.timeout = 0;\n\t\t\t}\n            req.send(JSON.stringify(payload));\n\t\t\tif (!req.responseText) {\n\t\t\t\t// Timeout, retry\n\t\t\t\treturn sendToServer(path, payload);\n\t\t\t}\n            return safeJSONParse(req.responseText);\n        } catch (ex) {\n            alert('Aardwolf encountered an error while sending data: ' + ex.toString());\n            listenToServer();\n        }\n    }\n\n    function replaceConsole() {\n        if (!window.console) {\n            window.console = {};\n        }\n\n        ['info', 'log', 'warn', 'error'].forEach(function(f) {\n            var oldFunc = window.console[f];\n\n            window.console[f] = function() {\n                var args = Array.prototype.slice.call(arguments);\n                /* Write to local console before writing to the potentially slow remote console.\n                   Make sure that the original function actually exists, otherwise this will\n                   case an error on WindowsPhone where the console object is not available. */\n                oldFunc && oldFunc.apply(window.console, args);\n\t\t\t\tif (consoleRedirected) {\n\t\t\t\t\tsendToServer('/console', {\n\t\t\t\t\t\tcommand: 'print-message',\n\t\t\t\t\t\ttype: f.toUpperCase(),\n\t\t\t\t\t\tmessage: args.toString()\n\t\t\t\t\t});\n\t\t\t\t}\n            };\n        });\n    }\n\n    function processCommand(cmd) {\n        switch (cmd.command) {\n\t\t\tcase 'update-redirect-console':\n\t\t\t\tconsoleRedirected = cmd.data;\n\t\t\t\treturn true;\n            case 'set-breakpoints':\n                breakpoints = {};\n                cmd.data.forEach(function(bp) {\n                    var file = bp[0];\n                    var line = bp[1];\n                    if (!breakpoints[file]) {\n                        breakpoints[file] = {};\n                    }\n                    breakpoints[file][line] = true;\n                });\n                return true;\n\n            case 'breakpoint-continue':\n                shouldBreak = function() { return false; };\n                return false;\n\n            case 'break-on-next':\n            case 'breakpoint-step':\n            case 'breakpoint-step-in':\n                shouldBreak = function() { return true; };\n                return false;\n\n            case 'breakpoint-step-out':\n                shouldBreak = (function(oldDepth) {\n                    return function(depth) {\n                        return depth < oldDepth;\n                    };\n                })(stackDepth);\n                return false;\n\n            case 'breakpoint-step-over':\n                shouldBreak = (function(oldDepth) {\n                    return function(depth) {\n                        return depth <= oldDepth;\n                    };\n                })(stackDepth);\n                return false;\n        }\n    }\n\n    function doEval(evalScopeFunc, cmd) {\n        var evalResult;\n        try {\n            evalResult = evalScopeFunc(cmd.data);\n        } catch (ex) {\n            evalResult = 'ERROR: ' + ex.toString();\n        }\n\t\tsendEvalResult(evalResult, cmd.data);\n    }\n\tfunction sendEvalResult(result, input) {\n\t\tif (result instanceof Object) {\n\t\t\tresult = JSON.stringify(result, function(k, v) {\n\t\t\t\tif (typeof v == \"function\") {\n\t\t\t\t\treturn \"[function]\";\n\t\t\t\t}\n\t\t\t\treturn v;\n\t\t\t});\n\t\t}\n\n\t\tsendToServer('/console', {\n\t\t\tcommand: 'print-eval-result',\n\t\t\tinput: input,\n\t\t\tresult: result\n\t\t});\n\t}\n\n    function getStack() {\n        var callstack = [];\n        var currentFunction = arguments.callee;\n\t\tvar i = 0;\n        while ((i < 10) && currentFunction) {\n            var fname = currentFunction.name || '<anonymous>';\n            callstack.push(fname);\n\t\t\tcurrentFunction = currentFunction.caller;\n\t\t\ti++;\n        }\n        return callstack;\n    }\n\n    function safeJSONParse(str) {\n        try {\n            return JSON.parse(str);\n        } catch (ex) {\n            return null;\n        }\n    }\n\n    this.init = function() {\n        replaceConsole();\n        var cmd = sendToServer('/init', {\n            command: 'mobile-connected'\n        });\n        if (cmd) {\n            processCommand(cmd);\n        }\n        listenToServer();\n    };\n\n    this.updatePosition = function(file, line, isDebuggerStatement, evalScopeFunc) {\n        /* Webkit's exceptions don't contain any useful file and line data,\n           so we keep track of this manually for exception reporting purposes. */\n        lastFile = file;\n        lastLine = line;\n\n        while (true) {\n            var isBreakpoint = (breakpoints[file] && breakpoints[file][line]) || /* explicit breakpoint? */\n                               isDebuggerStatement ||                            /* debugger; statement? */\n                               shouldBreak(stackDepth);                          /* step (in|over|out) or break-on-next? */\n\n            if (!isBreakpoint) {\n                return;\n            }\n\n            dropCommandConnection();\n\n            var cmd = sendToServer('/breakpoint', {\n                command: 'report-breakpoint',\n                file: file,\n                line: line,\n                stack: getStack().slice(1)\n            });\n            listenToServer();\n\n            if (!cmd) {\n                return;\n            }\n\n            if (cmd.command == 'eval') {\n                doEval(evalScopeFunc, cmd);\n            }\n            else {\n                var isInternalCommand = processCommand(cmd);\n                if (!isInternalCommand) {\n                    return;\n                }\n            }\n        }\n    };\n\n    this.reportException = function(e) {\n        sendToServer('/console', {\n            command: 'report-exception',\n            message: e.toString(),\n            file: lastFile,\n            line: lastLine,\n            stack: getStack().slice(1)\n        });\n    };\n\n\tthis.inspect = function(object) {\n\t\tif (object) {\n\t\t\tsendEvalResult(object, 'Aaardwolf.inspect(variable)');\n\t\t} else {\n\t\t\tsendEvalResult('ERROR: Undefined variable', 'Aaardwolf.inspect');\n\t\t}\n\t};\n\n    var stack = [];\n    var stackDepth = 0;\n\n    this.pushStack = function(functionName, file, line) {\n        stack.push([functionName, file, line]);\n        ++stackDepth;\n    };\n\n    this.popStack = function() {\n        var f = stack.pop();\n        --stackDepth;\n    };\n\n})();\n\nwindow.Aardwolf.init();\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"aardwolf-plus\",\n    \"description\": \"Aardwolf++ debugger\",\n    \"url\": \"http://lexandera.com/aardwolf\",\n    \"author\": \"Aleksander Kmetec <aleksander.kmetec@gmail.com>. Javier López <jlopez@tid.es>\",\n    \"dependencies\": {\n        \"coffee-script\": \"1.1.2\",\n        \"optimist\": \"0.2.6\",\n\t    \"node-watch\": \"0.2.4\",\n        \"prompt\": \"0.2.9\"\n    },\n    \"version\": \"0.0.0\"\n}\n"
  },
  {
    "path": "rewriter/coffeerewriter.js",
    "content": "'use strict';\n\nvar fs = require('fs');\nvar path = require('path');\n\nvar debugStatementTemplate = \n    fs.readFileSync(path.join(__dirname, 'templates/debug-template.coffee')).toString().trim();\nvar exceptionInterceptorTemplate = \n    fs.readFileSync(path.join(__dirname, 'templates/exception-template.coffee')).toString().replace(/\\n\\r?/g, '').replace(/ {4}/g, ' ');\n\nvar exceptionInterceptorParts = exceptionInterceptorTemplate.split('SPLIT');\nvar exceptionInterceptorStart = exceptionInterceptorParts[0].trim();\nvar exceptionInterceptorEnd = exceptionInterceptorParts[1].trim();\n\n\nfunction addDebugStatements(filePath, text) {\n    var coffee = require('coffee-script');\n    var lines = text.split('\\n');\n    var out = [];\n    \n    var breakpoints = [];\n    \n    function buildDebugStatement(file, line, isDebuggerStatement) {\n        breakpoints.push(line);\n        \n        return debugStatementTemplate\n                    .replace('__FILE__', file)\n                    .replace('__LINE__', line)\n                    .replace('__DEBUGGER__', isDebuggerStatement ? 'true' : 'false');\n    }\n    \n    \n    lines.forEach(function(line, i) {\n        var parts;\n        var lineNum = i+1;\n        \n        if (line.match(/^\\s*#/)) {\n            return;\n        }\n        \n        if (parts = line.match(/^(\\s*)[^\\s]+/)) {\n            var match = line.match(/^(\\s*)(debugger.*)$/);\n            var isDebuggerStatement = !!match; \n            out.push((parts[1] || '') + '('+ buildDebugStatement(filePath, lineNum, isDebuggerStatement) +');');\n            \n            if (isDebuggerStatement) {\n                /* Comment out the debugger statement to avoid triggering any native debuggers */\n                line = match[1] + '#' + match[2];\n            }\n            \n            out.push(line);\n        }\n    });\n\n    return {\n        file: exceptionInterceptorStart + coffee.compile(out.join('\\n')) + exceptionInterceptorEnd,\n        breakpoints: breakpoints\n    };\n}\n\n\nmodule.exports = {\n    addDebugStatements: addDebugStatements\n};\n\n"
  },
  {
    "path": "rewriter/jsrewriter.js",
    "content": "'use strict';\n\nvar fs = require('fs');\nvar path = require('path');\nvar jstok = require('./jstokenizer.js');\n\nvar debugStatementTemplate =\n    fs.readFileSync(path.join(__dirname, 'templates/debug-template.js')).toString().trim();\nvar exceptionInterceptorTemplate =\n    fs.readFileSync(path.join(__dirname, 'templates/exception-template.js')).toString().replace(/\\n\\r?/g, '').replace(/ {4}/g, ' ');\n\nvar exceptionInterceptorParts = exceptionInterceptorTemplate.split('SPLIT');\nvar exceptionInterceptorStart = exceptionInterceptorParts[0].trim();\nvar exceptionInterceptorEnd = exceptionInterceptorParts[1].trim();\n\nfunction buildExceptionInterceptorStart(functionName, file, line) {\n    return exceptionInterceptorStart\n                .replace('__FUNCTION__', functionName)\n                .replace('__FILE__', file)\n                .replace('__LINE__', line);\n}\n\nfunction addDebugStatements(filePath, text) {\n    var nestingDepth = [0];\n    var out = [];\n    var line = 1;\n    var semicolonOrFunctionBoundryEncountered = true;\n    var newlineEncountered = true;\n    var functionEncountered = false;\n    var wordAfterFunction = null;\n\tvar prevToken = null;\n\tvar openSwitch = false;\n\tvar openCase = false;\n\n\tvar invalidTokens = ['(', '[', ',', '=', ':', 'return', '|', '?'];\n\n\tvar breakpoints = [];\n\n\tfunction buildDebugStatement(file, line, isDebuggerStatement) {\n\t\tbreakpoints.push(line);\n\t\treturn debugStatementTemplate\n\t\t\t.replace('__FILE__', file)\n\t\t\t.replace('__LINE__', line)\n\t\t\t.replace('__DEBUGGER__', isDebuggerStatement ? 'true' : 'false');\n\t}\n\n    jstok.tokenize(text, function(token, type) {\n        /* drop carriage returns... we don't need them. */\n        if (token === '\\r') {\n            return;\n        }\n\n\t\tif (token == 'switch') {\n\t\t\topenSwitch = true;\n\t\t}\n\t\tif (token == 'case') {\n\t\t\topenCase = true;\n\t\t}\n\n        if (token == '\"use strict\"' || token == \"'use strict'\") {\n            token = '/* Aardwolf cannot work in strict mode. Disabling. '+token.split('').join('_')+' */';\n        }\n\n        /*\n            Whenever we encounter some code:\n            - if it's after a semicolon and a newline, or at the beginning of a function,\n              insert a debug statement in front of it\n            - it it's anywhere else, reset the semicolon and newline flags because we're not\n              anywhere near a place where a debug statement should be inserted\n\n            Yes, this rewriter assumes that you're using semicolons in your code.\n        */\n        if (['word', 'number', 'string', 'char'].indexOf(type) > -1) {\n            if (type != 'char' &&\n                token != 'else' &&\n                semicolonOrFunctionBoundryEncountered &&\n                newlineEncountered)\n            {\n                var isDebuggerStatement = token === 'debugger';\n                out.push(buildDebugStatement(filePath, line, isDebuggerStatement));\n\n                if (isDebuggerStatement) {\n                    /* Comment out the debugger statement to avoid triggering any native debuggers */\n                    token = '/*' + token + '*/';\n                }\n            }\n\n            semicolonOrFunctionBoundryEncountered = false;\n            newlineEncountered = false;\n        }\n\n        if (type == 'word') {\n            if (functionEncountered) {\n                wordAfterFunction = token;\n            }\n            functionEncountered = false;\n        }\n\n        if (token === 'function') {\n            /* keep a separate nesting depth counter for each nested function */\n            nestingDepth.push(0);\n            out.push(token);\n\n            functionEncountered = true;\n            wordAfterFunction = null;\n        }\n        else if (token === '{') {\n            ++nestingDepth[nestingDepth.length-1];\n\n            out.push(token);\n\n            /* we have just entered a function body - insert the first part of the exception interception block */\n            if (nestingDepth.length > 1 && nestingDepth[nestingDepth.length-1] === 1) {\n                out.push(buildExceptionInterceptorStart(wordAfterFunction || '<anonymous>', filePath, line));\n            }\n\n\t\t\tif (openSwitch) {\n\t\t\t\topenSwitch = false;\n\t\t\t} else if (invalidTokens.indexOf(prevToken) < 0) {\n\t\t\t\tsemicolonOrFunctionBoundryEncountered = true;\n\t\t\t}\n        }\n        else if (token === '}') {\n            --nestingDepth[nestingDepth.length-1];\n\n            /* we are about to exit a function body - insert the last part of the exception interception block */\n            if (nestingDepth.length > 1 && nestingDepth[nestingDepth.length-1] === 0) {\n                out.push(exceptionInterceptorEnd);\n                nestingDepth.pop();\n            }\n\t\t\tsemicolonOrFunctionBoundryEncountered = true;\n\n            out.push(token);\n        }\n\t\telse if (token === ':' && openCase) {\n\t\t\topenCase = false;\n\t\t\tsemicolonOrFunctionBoundryEncountered = true;\n\t\t\tout.push(token);\n\t\t}\n        else {\n            if (token === ';') {\n                semicolonOrFunctionBoundryEncountered = true;\n            }\n            else if (token === '\\n') {\n                ++line;\n                newlineEncountered = true;\n            }\n            else if (type == 'comment') {\n                /* comments can span multiple lines so we need to adjust line count accordingly */\n                var parts = token.split('\\n');\n                line += parts.length - 1;\n            }\n            out.push(token);\n        }\n\t\tif (type != 'comment' && type != 'whitespace' && type != 'newline') {\n\t\t\tprevToken = token;\n\t\t}\n    });\n\n\n\treturn {\n\t\tfile: buildExceptionInterceptorStart('<toplevel>', filePath, 0) + out.join('') + exceptionInterceptorEnd,\n\t\tbreakpoints: breakpoints\n\t};\n}\n\n\nmodule.exports = {\n    addDebugStatements: addDebugStatements\n};\n\n"
  },
  {
    "path": "rewriter/jstokenizer.js",
    "content": "'use strict';\n\n/* A simple JS tokenizer. We're really only interested in a couple of keywords, parentheses, \n   brackets and semicolons, so it doesn't need to be complete as long as it correctly handles\n   multi-word tokens such as strings and comments.\n*/\nfunction tokenize(str, onToken) {\n    var len = str.length;\n    var pos = 0;\n    var validRegexPos = false;\n    \n    while (pos < len) {\n        var c = str[pos];\n        \n        if (c === '\"' || c === \"'\") {\n            extractString(c);\n        }\n        else if (c === '/' && str[pos+1] === '/') {\n            extractSingleLineComment();\n        }\n        else if (c === '/' && str[pos+1] === '*') {\n            extractMultiLineComment();\n        }\n        else if (c === '/' && validRegexPos) {\n            extractRegexLiteral();\n        }\n        else if (c === ' ' || c === '\\t') {\n            extractWhitespace();\n        }\n        else if ('0123456789'.indexOf(c) > -1) {\n            extractNumber();\n        }\n        else if (c.match(/^[a-zA-Z_$]$/) !== null) {\n            extractWord();\n        }\n        else {\n            extractChar();\n        }\n    }\n    \n    function onTokenInternal(token, type) {\n        /* A slash following an assigment operator, a semicolon or an \n           opening paren can be a regex literal delimiter. */\n        if (type === 'char' && ':=;({'.indexOf(token) > -1) {\n            validRegexPos = true;\n        } else if (!((['comment', 'whitespace']).indexOf(type) > -1)) {\n            validRegexPos = false;\n        }\n        \n        onToken(token, type);\n    }\n    \n    function extractSingleLineComment() {\n        var endPos = str.indexOf(\"\\n\", pos);\n        if (endPos === -1) {\n            endPos = len - 1;\n        }\n        onTokenInternal(str.substring(pos, endPos), 'comment');\n        pos = endPos;\n    }\n    \n    function extractMultiLineComment() {\n        var endPos = pos;\n        while (!(str[++endPos] === '*' && str[endPos+1] === '/'));\n        endPos += 2;\n        onTokenInternal(str.substring(pos, endPos), 'comment');\n        pos = endPos;\n    }\n    \n    function extractRegexLiteral() {\n        var endPos = pos;\n        /* regex literal body /.../ */\n        while (str[++endPos] != '/') {\n            if (str[endPos] == '\\\\') {\n                ++endPos;\n            }\n        }\n        /* flags following the body */\n        while ('gimy'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'regex');\n        pos = endPos;\n    }\n    \n    function extractString(quoteChar) {\n        var endPos = pos;\n        while (str[++endPos] != quoteChar) {\n            if (str[endPos] == '\\\\') {\n                ++endPos;\n            }\n        }\n        ++endPos;\n        onTokenInternal(str.substring(pos, endPos), 'string');\n        pos = endPos;\n    }\n    \n    function extractNumber() {\n        var endPos = pos;\n        while ('0123456789.eE'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'number');\n        pos = endPos;\n    }\n    \n    function extractWord() {\n        var endPos = pos;\n        while (str[++endPos].match(/^[a-zA-Z_$0-9]$/) !== null);\n        onTokenInternal(str.substring(pos, endPos), 'word');\n        pos = endPos;\n    }\n    \n    function extractWhitespace() {\n        var endPos = pos;\n        while (' \\t'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'whitespace');\n        pos = endPos;\n    }\n    \n    function extractChar() {\n        var c = str.substr(pos, 1);\n        onTokenInternal(c, c === '\\n' ? 'newline' : 'char');\n        ++pos;\n    }\n}\n\nmodule.exports.tokenize = tokenize;"
  },
  {
    "path": "rewriter/multirewriter.js",
    "content": "'use strict';\n\nvar path = require('path');\nvar fs = require('fs');\n\nvar config = require('../config/config.defaults.js');\n\nfunction isRewritable(filePath) {\n    var fileServerBaseDir = path.normalize(config.fileServerBaseDir);\n    var fullRequestedFilePath = path.join(fileServerBaseDir, filePath);\n    \n    /* File must exist and must be located inside the fileServerBaseDir */\n    if (fs.existsSync(fullRequestedFilePath) &&\n        fs.statSync(fullRequestedFilePath).isFile() &&\n        fullRequestedFilePath.indexOf(fileServerBaseDir) === 0)\n    {\n        if (filePath.substr(-3) == '.js' || filePath.substr(-7) == '.coffee') {\n            return true;\n        }\n        \n        return false;\n    }\n}\n\nfunction getRewrittenContent(filePath) {\n    var fileServerBaseDir = path.normalize(config.fileServerBaseDir);\n    var fullRequestedFilePath = path.join(fileServerBaseDir, filePath);\n    \n    var rewriter;\n    \n    if (filePath.substr(-3) == '.js') {\n        rewriter = require('../rewriter/jsrewriter.js');\n    }\n    else if (filePath.substr(-7) == '.coffee') {\n        rewriter = require('../rewriter/coffeerewriter.js');\n    }\n    \n    if (rewriter) {\n        var content = fs.readFileSync(fullRequestedFilePath).toString();\n        return rewriter.addDebugStatements(filePath, content);\n    }\n\n}\n\nmodule.exports = {\n    getRewrittenContent: getRewrittenContent,\n    isRewritable: isRewritable\n};\n\n"
  },
  {
    "path": "rewriter/templates/debug-template.coffee",
    "content": "\nAardwolf.updatePosition(\"__FILE__\", __LINE__, __DEBUGGER__, (aardwolfEval) -> eval(aardwolfEval));\n"
  },
  {
    "path": "rewriter/templates/debug-template.js",
    "content": "\nAardwolf.updatePosition(\"__FILE__\", __LINE__, __DEBUGGER__, aardwolfEvalFunc);\n\n"
  },
  {
    "path": "rewriter/templates/exception-template.coffee",
    "content": "\ntry {\n  SPLIT\n} catch (aardwolfEx) {\n    if (!aardwolfEx.rethrown) {\n        Aardwolf.reportException(aardwolfEx);\n    }\n    aardwolfEx.rethrown = true;\n    throw aardwolfEx;\n}\n"
  },
  {
    "path": "rewriter/templates/exception-template.js",
    "content": "\nvar __this = this;try {\n  Aardwolf.pushStack(\"__FUNCTION__\", \"__FILE__\", '__LINE__');\n  var aardwolfEvalFunc = function(aardwolfEval) { return eval(aardwolfEval); };\n  SPLIT\n} catch (aardwolfEx) {\n    if (!aardwolfEx.rethrown) {\n        Aardwolf.reportException(aardwolfEx);\n    }\n    aardwolfEx.rethrown = true;\n    throw aardwolfEx;\n} finally {\n  Aardwolf.popStack();\n}\n"
  },
  {
    "path": "samples/calc-coffee.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>COFFEESCRIPT Calculator</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n        \n        <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js\"></script>\n        \n        <script type=\"text/javascript\" src=\"http://__SERVER_HOST__:__FILE_SERVER_PORT__/aardwolf.js\"></script>\n        <script type=\"text/javascript\" src=\"http://__SERVER_HOST__:__FILE_SERVER_PORT__/calc.coffee\"></script>\n        \n        <style></style>\n    </head>\n    \n    <body>\n    \n    Take <input type=\"number\" id=\"number1\" /> <br/>\n    add <input type=\"number\" id=\"number2\" /> to it <br/>\n    and multiply by <input type=\"number\" id=\"number3\" />.<br/>\n    <br/>\n    You get: <span id=\"result\"></span>\n    <br/>\n    <br/>\n    <button id=\"calculate\">Go!</button>\n    <button id=\"reset\">reset</button>\n    \n    </body>\n</html>\n"
  },
  {
    "path": "samples/calc.coffee",
    "content": "\n$number1 = null\n$number2 = null\n$number3 = null\n$result = null\n\n$ ->\n    # Locate number input fields\n    $number1 = $ '#number1'\n    $number2 = $ '#number2'\n    $number3 = $ '#number3'\n    $result = $ '#result'\n    \n    ($ '#calculate').click calculate\n    ($ '#reset').click reset\n    \n# Performs calculation\ncalculate = ->\n    # read entered numbers\n    a = Number $number1.val()\n    b = Number $number2.val()\n    c = Number $number3.val()\n    \n    performAddition = (n1, n2) -> \n        console.log 'Performing addition of '+n1+' and '+n2+'.'\n        n1 + n2\n    \n    performMultiplication = (n1, n2) ->\n        console.log 'Performing multiplication of '+n1+' and '+n2+'.'\n        n1 * n2\n    \n    sum = performAddition a, b\n    total = performMultiplication sum, c\n    \n    # Update result field\n    $result.text total\n    \n# Clears the result and input fields\nreset = ->\n    debugger # the 'debugger' statement works also...\n    $number1.val ''\n    $number2.val ''\n    $number3.val ''\n    $result.text ''\n"
  },
  {
    "path": "samples/calc.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>Calculator</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n        \n        <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js\"></script>\n        \n        <script type=\"text/javascript\" src=\"http://__SERVER_HOST__:__FILE_SERVER_PORT__/aardwolf.js\"></script>\n        <script type=\"text/javascript\" src=\"http://__SERVER_HOST__:__FILE_SERVER_PORT__/calc.js\"></script>\n        \n        <style></style>\n    </head>\n    \n    <body>\n    \n    Take <input type=\"number\" id=\"number1\" /> <br/>\n    add <input type=\"number\" id=\"number2\" /> to it <br/>\n    and multiply by <input type=\"number\" id=\"number3\" />.<br/>\n    <br/>\n    You get: <span id=\"result\"></span>\n    <br/>\n    <br/>\n    <button id=\"calculate\">Go!</button>\n    <button id=\"reset\">reset</button>\n    \n    </body>\n</html>\n"
  },
  {
    "path": "samples/calc.js",
    "content": "\nvar $number1;\nvar $number2;\nvar $number3;\nvar $result;\n\n$(function() {\n    /* Locate number input fields */\n    $number1 = $('#number1');\n    $number2 = $('#number2');\n    $number3 = $('#number3');\n    $result = $('#result');\n    \n    $('#calculate').click(calculate);\n    $('#reset').click(reset);\n});\n\n/**\n * Performs calculation\n */\nfunction calculate() {\n    /* Read entered numbers */\n    var a = Number($number1.val());\n    var b = Number($number2.val());\n    var c = Number($number3.val());\n\n    function performAddition(n1, n2) {\n        console.log('Performing addition of '+n1+' and '+n2+'.');\n        return n1 + n2;\n    }\n    \n    function performMultiplication(n1, n2) {\n        console.log('Performing multiplication of '+n1+' and '+n2+'.');\n        \n        setTimeout(function() {\n            /* Intended to demonstrate exception reporting in async calls */\n            nonExistingObjectForExceptionReportingDemonstration.bar();\n        }, 1000);\n        \n        return n1 * n2;\n    }\n    \n    var sum = performAddition(a, b);\n    var total = performMultiplication(sum, c);\n    \n    /* Update result field */\n    $result.text(total);\n}\n\n/**\n * Clears the result and input fields\n */\nfunction reset() {\n    debugger; // the 'debugger' statement works also...\n    $number1.val('');\n    $number2.val('');\n    $number3.val('');\n    $result.text('');\n}\n\n"
  },
  {
    "path": "server/debug-file-server.js",
    "content": "'use strict';\n\n/*\n * Serves source files with debug statements inserted.\n */\n\nvar http = require('http');\nvar path = require('path');\nvar fs = require('fs');\nvar url = require('url');\n\nvar config = require('../config/config.defaults.js');\nvar util = require('./server-util.js');\n\nvar multirewriter = require('../rewriter/multirewriter.js')\n\nfunction run() {\n    if (!fs.existsSync(config.fileServerBaseDir)) {\n        console.error('ERROR: Path does not exist: ' + config.fileServerBaseDir);\n        process.exit(1);\n    }\n\n    http.createServer(DebugFileServer).listen(config.fileServerPort, null, function() {\n        console.log('File server listening for requests on port ' + config.fileServerPort + '.');\n    });\n}\n\n\nfunction DebugFileServer(req, res) {\n    var requestedFile = url.parse(req.url).pathname;\n    var fileServerBaseDir = path.normalize(config.fileServerBaseDir);\n    var fullRequestedFilePath = path.join(fileServerBaseDir, requestedFile);\n\n    \n    /* alias for serving the debug library */\n    if (requestedFile.toLowerCase() == '/aardwolf.js') {\n        util.serveStaticFile(res, path.join(__dirname, '../js/aardwolf.js'));\n    }\n    /* File must exist and must be located inside the fileServerBaseDir */\n    else if (fs.existsSync(fullRequestedFilePath) &&\n             fs.statSync(fullRequestedFilePath).isFile() &&\n             fullRequestedFilePath.indexOf(fileServerBaseDir) === 0)\n    {\n        if (multirewriter.isRewritable(requestedFile)) {\n            var processedFile = multirewriter.getRewrittenContent(requestedFile);\n            res.writeHead(200, {'Content-Type': 'application/javascript'});\n            res.end(processedFile.file);\n        }\n        else {\n            util.serveStaticFile(res, fullRequestedFilePath);\n        }\n    }\n    else {\n        res.writeHead(404, {'Content-Type': 'text/plain'});\n        res.end('NOT FOUND');\n    }\n}\n\n\nmodule.exports.run = run;\n"
  },
  {
    "path": "server/rewriter-server.js",
    "content": "'use strict';\n\n/*\n * Serves source files with debug statements inserted.\n */\n\nvar path = require('path');\nvar fs = require('fs');\nvar watch = require('node-watch');\n\nvar config = require('../config/config.defaults.js');\nvar util = require('./server-util.js');\nvar rewriter = require('../rewriter/jsrewriter.js');\n\nvar cachePath,\n    fileCache = {\n        files: {},\n        whiteList: [],\n        blackList: []\n    };\n\n\nfunction run() {\n    if (!fs.existsSync(config.fileServerBaseDir)) {\n        console.error('ERROR: Path does not exist: ' + config.fileServerBaseDir);\n        process.exit(1);\n    }\n\n    processFiles();\n\n    var serverBaseDir = path.normalize(config.fileServerBaseDir);\n    // Watch for file changes\n    watch(serverBaseDir, function(fileName) {\n        if (fileName) {\n            for (var i = 0; i < config.ignoreFiles.length; i++) {\n                if (fileName.indexOf(config.ignoreFiles[i]) >= 0) {\n                    return;\n                }\n            }\n            var file = fileName.substr(serverBaseDir.length);\n            file = file.replace(/\\\\/g, '/');\n            processFile(file, true);\n        }\n    });\n}\n\nfunction processFiles() {\n    var files = util.getAllFiles(),\n        destBaseDir = path.normalize(config.outputDir),\n        content;\n\n    if (!fs.existsSync(destBaseDir)) {\n        fs.mkdirSync(destBaseDir);\n    }\n    cachePath = path.join(destBaseDir, 'cache.json');\n\n    if (fs.existsSync(cachePath)) {\n        fileCache = JSON.parse(fs.readFileSync(cachePath).toString());\n\n        // Flush cache if white or black list has changed\n        if (!fileCache.blackList.equals(config.blackList) || !fileCache.whiteList.equals(config.whiteList)) {\n            fileCache = {\n                files: {},\n                whiteList: config.whiteList,\n                blackList: config.blackList\n            }\n        }\n    }\n\n    for (var i = 0; i < files.length; i++) {\n        processFile(files[i], false);\n    }\n\n    // Copy Aardwolf script\n    content =  fs.readFileSync(path.join(__dirname, '../js/aardwolf.js'));\n\n    content = content\n        .toString()\n        .replace(/__SERVER_HOST__/g, config.serverHost)\n        .replace(/__SERVER_PORT__/g, config.serverPort)\n        .replace(/__FILE_SERVER_PORT__/g, config.fileServerPort);\n\n    fs.writeFileSync(path.join(destBaseDir, 'aardwolf.js'), content);\n\n    fs.writeFileSync(cachePath, JSON.stringify(fileCache));\n}\n\nfunction processFile(fileName, writeCache) {\n\n    var serverBaseDir = path.normalize(config.fileServerBaseDir),\n        destBaseDir = path.normalize(config.outputDir),\n        origFilePath = path.join(serverBaseDir, fileName),\n        destFilePath = path.join(destBaseDir, fileName),\n        fileStat;\n\n    if (!fs.existsSync(origFilePath)) {\n        return;\n    }\n\n    fileStat = fs.statSync(origFilePath);\n\n    if (fileStat.isDirectory()) {\n        try {\n            fs.mkdirSync(destFilePath);\n        } catch(e) {\n            // The folder already exists\n        }\n        return;\n    }\n\n    log('Processing ' + fileName + '... ');\n\n    if (fileCache.files[fileName] && (fileStat.mtime.getTime() == fileCache.files[fileName].time) &&\n        fs.existsSync(destFilePath)) {\n        log('Skipping\\n');\n        return;\n    }\n\n    var breakpoints = [];\n\n    var mustDebug = true;\n    for (var i = 0; i < config.blackList.length; i++) {\n        if (fileName.indexOf(config.blackList[i]) >= 0) {\n            mustDebug = false;\n            break;\n        }\n    }\n\n    if (mustDebug && config.whiteList.length > 0) {\n        mustDebug = false;\n        for (i = 0; i < config.whiteList.length; i++) {\n            if (fileName.indexOf(config.whiteList[i]) >= 0) {\n                mustDebug = true;\n                break;\n            }\n        }\n    }\n\n    if ((mustDebug && fileName.substr(-3) === '.js') || fileName === config.indexFile) {\n\n        var content = fs.readFileSync(origFilePath).toString();\n        if (fileName === config.indexFile) {\n            // Inject aardwolf script in index\n            var where = content.indexOf(config.whereToInsertAardwolf) + config.whereToInsertAardwolf.length;\n\n            content = [content.slice(0, where), '\\n', config.aardwolfScript, '\\n', content.slice(where)].join('');\n        } else {\n            // Instrument JS code\n            log('Debugging ');\n            var processedFile = rewriter.addDebugStatements(fileName, content);\n            content = processedFile.file;\n            breakpoints = processedFile.breakpoints;\n        }\n        fs.writeFileSync(destFilePath, content);\n    } else {\n        util.copyFileSync(origFilePath, destFilePath);\n    }\n\n    fileCache.files[fileName] = {\n        time: fileStat.mtime.getTime(),\n        breakpoints: breakpoints\n    };\n    if (writeCache) {\n        fs.writeFileSync(cachePath, JSON.stringify(fileCache));\n    }\n\n    log('OK\\n');\n}\n\n\n\nfunction log(m) {\n    if (config.verbose) {\n        process.stdout.write(m);\n    }\n}\n\nmodule.exports.run = run;\n"
  },
  {
    "path": "server/server-util.js",
    "content": "'use strict';\n\nvar fs = require('fs');\nvar path = require('path');\nvar config = require('../config/config.defaults.js');\n\nfunction serveStaticFile(res, filename) {\n    var ext = filename.split('.').reverse()[0];\n    var ct = ext == 'js'   ? 'application/javascript' :\n             ext == 'css'  ? 'text/css' :\n             ext == 'html' ? 'text/html' :\n             ext == 'png'  ? 'image/png' :\n             ext == 'jpg'  ? 'image/jpeg' :\n             ext == 'jpeg' ? 'image/jpeg' :\n             'text/plain';\n\n    var fdata = fs.readFileSync(filename);\n\n    if (['png', 'jpg', 'jpeg'].indexOf(ext) == -1) {\n        fdata = fdata\n            .toString()\n            .replace(/__SERVER_HOST__/g, config.serverHost)\n            .replace(/__SERVER_PORT__/g, config.serverPort)\n            .replace(/__FILE_SERVER_PORT__/g, config.fileServerPort);\n    }\n\n    res.writeHead(200, { 'Content-Type': ct });\n    res.end(fdata);\n}\n\n\nfunction getFilesList() {\n\treturn getAllFiles(['.js', '.coffee']);\n}\n\nfunction getAllFiles(extensions) {\n\tvar files = [];\n    var baseDir = path.normalize(config.fileServerBaseDir);\n\n    function walk(dir) {\n        var fileList = fs.readdirSync(dir);\n        fileList.forEach(function(f) {\n            var fullPath = path.join(dir, f);\n            var stat = fs.statSync(fullPath);\n\t\t\tvar listForDebug = !!extensions && extensions.length > 0;\n\t\t\tvar include = false;\n\t\t\tvar extension;\n\t\t\tvar i;\n\n\t\t\tif (!validFile(fullPath)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (listForDebug) {\n\t\t\t\tfor (i = 0; i < config.blackList.length; i++) {\n\t\t\t\t\tif (fullPath.indexOf(config.blackList[i]) >= 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stat.isFile() && config.whiteList.length > 0) {\n\t\t\t\t\tvar valid = false;\n\t\t\t\t\tfor (i = 0; i < config.whiteList.length; i++) {\n\n\t\t\t\t\t\tif (fullPath.indexOf(config.whiteList[i]) >= 0) {\n\t\t\t\t\t\t\tvalid = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!valid) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n            if (stat.isFile()) {\n\t\t\t\tif (listForDebug) {\n\t\t\t\t\tfor (i = 0; i < extensions.length; i++) {\n\t\t\t\t\t\textension = extensions[i];\n\t\t\t\t\t\tif (fullPath.substr(-(extension.length)) == extension) {\n\t\t\t\t\t\t\tinclude = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tinclude = true;\n\t\t\t\t}\n                if (include) {\n                    files.push(fullPath.substr(baseDir.length));\n                }\n            }\n            else {\n\t\t\t\tif (!listForDebug) {\n\t\t\t\t\tfiles.push(fullPath.substr(baseDir.length));\n\t\t\t\t}\n                walk(fullPath);\n            }\n        });\n    }\n\n    walk(baseDir);\n\n    /* Unixify paths */\n    files = files.map(function(f) { return f.replace(/\\\\/g, '/'); });\n\n    return files;\n}\n\nfunction validFile(path) {\n\tfor (var i = 0; i < config.ignoreFiles.length; i++) {\n\t\tif (path.indexOf(config.ignoreFiles[i]) >= 0) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nfunction copyFileSync (srcFile, destFile) {\n\tvar BUF_LENGTH = 64*1024;\n\tvar buff = new Buffer(BUF_LENGTH);\n\tvar fdr = fs.openSync(srcFile, 'r');\n\tvar fdw = fs.openSync(destFile, 'w');\n\tvar bytesRead = 1;\n\tvar pos = 0\n\twhile (bytesRead > 0) {\n\t\tbytesRead = fs.readSync(fdr, buff, 0, BUF_LENGTH, pos);\n\t\tfs.writeSync(fdw, buff, 0, bytesRead);\n\t\tpos += bytesRead;\n\t}\n\tfs.closeSync(fdr);\n\tfs.closeSync(fdw);\n}\n\nmodule.exports.serveStaticFile = serveStaticFile;\nmodule.exports.getFilesList = getFilesList;\nmodule.exports.getAllFiles = getAllFiles;\nmodule.exports.copyFileSync = copyFileSync;\n\n"
  },
  {
    "path": "server/server.js",
    "content": "'use strict';\n\n/*\n * Server for communication between the UI and the mobile library.\n * Also serves UI-related files.\n */\n\nvar http = require('http');\nvar path = require('path');\nvar fs = require('fs');\n\nvar config = require('../config/config.defaults.js');\nvar util = require('./server-util.js');\n\nfunction run() {\n    /* Server for web service ports and debugger UI */\n    http.createServer(AardwolfServer).listen(config.serverPort, null, function() {\n        console.log('Server listening for requests on port ' + config.serverPort + '.');\n    });\n}\n\nvar mobileDispatcher = new Dispatcher();\nvar desktopDispatcher = new Dispatcher();\n\nfunction AardwolfServer(req, res) {\n    res.setHeader('Cache-Control', 'no-cache');\n    res.setHeader('Access-Control-Allow-Origin', '*');\n    res.setHeader('Access-Control-Allow-Headers', 'Origin, Content-Type');\n    res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');\n    res.setHeader('Access-Control-Allow-Credentials', 'true');\n\n    var body = '';\n\n    if (req.method == 'OPTIONS') {\n        res.end();\n        return;\n    }\n    else if (req.method == 'POST') {\n        req.on('data', function (chunk) { body += chunk; });\n        req.on('end', function () { processPostedData(JSON.parse(body)); });\n    }\n    else {\n        processPostedData();\n    }\n\n    function processPostedData(data) {\n        switch (req.url) {\n            case '/mobile/init':\n                mobileDispatcher.end();\n                mobileDispatcher = new Dispatcher();\n                mobileDispatcher.setClient(res);\n                desktopDispatcher.clearMessages();\n                desktopDispatcher.addMessage(data);\n                break;\n\n            case '/mobile/console':\n                desktopDispatcher.addMessage(data);\n                ok200();\n                break;\n\n            case '/mobile/breakpoint':\n                desktopDispatcher.addMessage(data);\n                mobileDispatcher.setClient(res);\n                break;\n\n            case '/mobile/incoming':\n                mobileDispatcher.setClient(res);\n                break;\n\n            case '/desktop/outgoing':\n                mobileDispatcher.addMessage(data);\n                ok200();\n                break;\n\n            case '/desktop/incoming':\n                desktopDispatcher.setClient(res);\n                break;\n\n            case '/files/list':\n                ok200({ files: util.getFilesList() });\n                break;\n\n            case '/':\n            case '/ui':\n            case '/ui/':\n                res.writeHead(302, {'Location': '/ui/index.html'});\n                res.end();\n                break;\n\n            default:\n                /* check if we need to serve a UI file */\n                if (req.url.indexOf('/ui/') === 0) {\n                    var requestedFile = req.url.substr(4);\n                    var uiFilesDir = path.join(__dirname, '../ui/');\n                    var fullRequestedFilePath = path.join(uiFilesDir, requestedFile);\n\n                    /* File must exist and must be located inside the uiFilesDir */\n                    if (fs.existsSync(fullRequestedFilePath) && fullRequestedFilePath.indexOf(uiFilesDir) === 0) {\n                        util.serveStaticFile(res, fullRequestedFilePath);\n                        break;\n                    }\n                }\n\n                /* check if we need to serve a UI file */\n                if (req.url.indexOf('/files/data/') === 0) {\n                    var requestedFile = req.url.substr(12);\n                    var filesDir = path.normalize(config.fileServerBaseDir);\n                    var fullRequestedFilePath = path.join(filesDir, requestedFile);\n\n                    /* File must exist and must be located inside the filesDir */\n                    if (fs.existsSync(fullRequestedFilePath) && fullRequestedFilePath.indexOf(filesDir) === 0) {\n                        ok200({\n                            data: fs.readFileSync(fullRequestedFilePath).toString(),\n                            breakpoints: require('../rewriter/multirewriter.js').getRewrittenContent(requestedFile).breakpoints || []\n                        });\n                        break;\n                    }\n                }\n\n                /* fallback... */\n                res.writeHead(404, {'Content-Type': 'text/plain'});\n                res.end('NOT FOUND');\n        }\n    }\n\n    function ok200(data) {\n        res.writeHead(200, { 'Content-Type': 'application/json' });\n        res.end(JSON.stringify(data || {}));\n    }\n}\n\n\nfunction Dispatcher() {\n    var queue = [];\n    var client;\n\n    this.setClient = function(c) {\n        this.end();\n        client = c;\n        process();\n    };\n\n    this.addMessage = function(m) {\n        queue.push(m);\n        process();\n    };\n\n    this.end = function() {\n        if (client) {\n            client.end();\n        }\n    };\n\n    this.clearMessages = function() {\n        queue = [];\n    };\n\n    function process() {\n        if (client && queue.length > 0) {\n            client.writeHead(200, { 'Content-Type': 'application/json' });\n            var msg = queue.shift();\n            client.end(JSON.stringify(msg));\n            client = null;\n        }\n    }\n}\n\nmodule.exports.run = run;\n"
  },
  {
    "path": "ui/css/buttons.css",
    "content": "/*\n * Just some other awesome CSS3 buttons\n * http://www.red-team-design.com/just-another-awesome-css3-buttons\n */\n\n.button,\n.button.silver\n{\n    display: inline-block;\n    white-space: nowrap;\n    background-color: #ccc;\n    background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#ccc));\n    background-image: -webkit-linear-gradient(top, #eee, #ccc);\n    background-image: -moz-linear-gradient(top, #eee, #ccc);\n    background-image: -ms-linear-gradient(top, #eee, #ccc);\n    background-image: -o-linear-gradient(top, #eee, #ccc);\n    background-image: linear-gradient(top, #eee, #ccc);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#cccccc');\n    border: 1px solid #777;\n    padding: 0 1.5em;\n    margin: 0.5em 0.5em 0.5em 0;\n    font: bold 1em/1.9em Arial, Helvetica;\n    text-decoration: none;\n    color: #333;\n    text-shadow: 0 1px 0 rgba(255,255,255,.8);\n    -moz-border-radius: .3em;\n    -webkit-border-radius: .3em;\n    border-radius: .3em;\n    -moz-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);\n    -webkit-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);\n    box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);\n}\n\n.button:hover,\n.button.silver:hover\n{\n    background-color: #ddd;\n    background-image: -webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#ddd));\n    background-image: -webkit-linear-gradient(top, #fafafa, #ddd);\n    background-image: -moz-linear-gradient(top, #fafafa, #ddd);\n    background-image: -ms-linear-gradient(top, #fafafa, #ddd);\n    background-image: -o-linear-gradient(top, #fafafa, #ddd);\n    background-image: linear-gradient(top, #fafafa, #ddd);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa', EndColorStr='#dddddd');\n}\n\n.button:active\n{\n    -moz-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;\n    -webkit-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;\n    box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;\n    position: relative;\n    top: 1px;\n}\n\n\n.button.small {\n    font-size:0.75em;\n}\n\n\n\n.button.green\n{\n    background-color: #DDF906;\n    background-image: -webkit-linear-gradient(top, #e8ff38, #92be13);\n    background-image: -moz-linear-gradient(top,    #e8ff38, #92be13);\n    background-image: -ms-linear-gradient(top,     #e8ff38, #92be13);\n    background-image: -o-linear-gradient(top,      #e8ff38, #92be13);\n    background-image: linear-gradient(top,         #e8ff38, #92be13);\n    border: 1px solid #777;\n}\n\n\n.button.green:hover\n{\n    background-color: #DDF906;\n    background-image: -webkit-linear-gradient(top, #e8ff38, #a8db16);\n    background-image: -moz-linear-gradient(top,    #e8ff38, #a8db16);\n    background-image: -ms-linear-gradient(top,     #e8ff38, #a8db16);\n    background-image: -o-linear-gradient(top,      #e8ff38, #a8db16);\n    background-image: linear-gradient(top,         #e8ff38, #a8db16);\n    border: 1px solid #777;\n}\n\n\n\n\n\n.button.blue\n{\n    background-color: #DDF906;\n    background-image: -webkit-linear-gradient(top, #92e3fe, #4d9cee);\n    background-image: -moz-linear-gradient(top,    #92e3fe, #4d9cee);\n    background-image: -ms-linear-gradient(top,     #92e3fe, #4d9cee);\n    background-image: -o-linear-gradient(top,      #92e3fe, #4d9cee);\n    background-image: linear-gradient(top,         #92e3fe, #4d9cee);\n    border: 1px solid #777;\n}\n\n\n.button.blue:hover\n{\n    background-color: #DDF906;\n    background-image: -webkit-linear-gradient(top, #92e3fe, #81d1fe);\n    background-image: -moz-linear-gradient(top,    #92e3fe, #81d1fe);\n    background-image: -ms-linear-gradient(top,     #92e3fe, #81d1fe);\n    background-image: -o-linear-gradient(top,      #92e3fe, #81d1fe);\n    background-image: linear-gradient(top,         #92e3fe, #81d1fe);\n    border: 1px solid #777;\n}\n\n\n/*\n.button:focus\n{\n    outline: 0;\n    background: #fafafa;\n}\n*/\n\n.button:before\n{\n    background: #ccc;\n    background: rgba(0,0,0,.1);\n    float: left;\n    width: 1em;\n    text-align: center;\n    font-size: 1.5em;\n    margin: 0 1em 0 -1em;\n    padding: 0 .2em;\n    -moz-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);\n    -webkit-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);\n    box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);\n    -moz-border-radius: .20em 0 0 .20em;\n    -webkit-border-radius: .20em 0 0 .20em;\n    border-radius: .20em 0 0 .20em;\n}\n\n/* Buttons and inputs */\n\nbutton.button, input.button\n{\n    cursor: pointer;\n    overflow: visible; /* removes extra side spacing in IE */\n}\n\n/* removes extra inner spacing in Firefox */\nbutton::-moz-focus-inner\n{\n  border: 0;\n  padding: 0;\n}\n\n/* If line-height can't be modified, then fix Firefox spacing with padding */\n input::-moz-focus-inner\n{\n  padding: .4em;\n}\n\n/* The disabled styles */\n.button[disabled], .button[disabled]:hover, .button.disabled, .button.disabled:hover\n{\n    background: #eee;\n    color: #aaa;\n    border-color: #aaa;\n    cursor: default;\n    text-shadow: none;\n    position: static;\n    -moz-box-shadow: none;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n}\n\n/* Hexadecimal entities for the icons */\n\n.add:before\n{\n    content: \"\\271A\";\n}\n\n.edit:before\n{\n    content: \"\\270E\";\n}\n\n.delete:before\n{\n    content: \"\\2718\";\n}\n\n.save:before\n{\n    content: \"\\2714\";\n}\n\n.email:before\n{\n    content: \"\\2709\";\n}\n\n.like:before\n{\n    content: \"\\2764\";\n}\n\n.next:before\n{\n    content: \"\\279C\";\n}\n\n.step-over:before\n{\n    content: \"\\21B7\";\n}\n\n.step-in:before\n{\n    content: \"\\21B4\";\n}\n\n.step-out:before\n{\n    content: \"\\21B1\";\n}\n\n.star:before\n{\n    content: \"\\2605\";\n}\n\n.spark:before\n{\n    content: \"\\2737\";\n}\n\n.play:before\n{\n    content: \"\\25B6\";\n}\n\n\n\n\n"
  },
  {
    "path": "ui/css/ui.css",
    "content": "\nbody, html {\n    height: 100%;\n    font-size: 14px;\n}\n\nbody {\n    font-family: Arial, Helvetica, Tahoma, Verdana, sans-serif;\n    margin: 0;\n    padding: 0px;\n}\n\ndiv, textarea {\n    -moz-box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    box-sizing: border-box;\n}\n\nh2 {\n    margin: 0;\n    margin-bottom: 5px;\n    padding: 0;\n    clear: both;\n    font-weight: bold;\n    font-size: 16px;\n\n    -moz-user-select: -moz-none;\n    -khtml-user-select: none;\n    -webkit-user-select: none;\n    user-select: none;\n    cursor: default;\n    color: #333;\n}\n\nh2:not(:first-of-type) {\n    margin-top: 10px;\n}\n\npre {\n    margin: 0;\n}\n\ntextarea {\n    font-family: \"Courier New\";\n    font-size: 14px;\n}\n\n#top-toolbar {\n    position: absolute;\n    height: 60px;\n    width: 100%;\n    z-index: 500;\n\n    padding: 8px 0 5px 10px;\n    border-bottom: 1px solid #aaa;\n\n    background-image: -webkit-linear-gradient(top, #F0F0F0, #ccc);\n    background-image: -moz-linear-gradient(top,    #F0F0F0, #ccc);\n    background-image: -ms-linear-gradient(top,     #F0F0F0, #ccc);\n    background-image: -o-linear-gradient(top,      #F0F0F0, #ccc);\n    background-image: linear-gradient(top,         #F0F0F0, #ccc);\n}\n\n#options {\n    position: absolute;\n    top: 10px;\n    right: 20px;\n}\n\n#center-panels {\n    position: relative;\n    display: block;\n    width: 100%;\n    height: 100%;\n\n    padding-top: 60px;\n    padding-right: 430px;\n    padding-bottom: 250px;\n\n    margin: 0;\n    border: 0;\n\n    overflow: hidden;\n    z-index: 400;\n}\n\n#code-container {\n    background: url(../img/code-bg.png);\n    background-position: -8px 0px;\n    background-repeat: repeat-y;\n    height: 100%;\n    overflow: auto;\n}\n\n#code {\n    white-space: pre;\n    font-family: \"Courier New\", monospace;\n    margin: 5px 0 50px 0;\n}\n\n#code .comment {\n    color: green;\n}\n\n#code .keyword {\n    color: blue;\n}\n\n#code .literal {\n    color: purple;\n}\n\n#code .string {\n    color: purple;\n}\n\n#code .number {\n    color: brown;\n}\n\n#code .linenum {\n    color: black;\n    cursor: pointer;\n    font-weight: bold;\n}\n#code .linenum.no-breakpoint {\n    color: grey;\n    cursor: default;\n    font-weight: normal;\n}\n\n#code .linenum.breakpoint {\n    font-weight: bold;\n    color: white;\n    background-color: red;\n}\n\n#sidebar {\n    display: block;\n    width: 430px;\n    padding: 5px;\n\n    position: absolute;\n    right: 0px;\n    top: 60px;\n    bottom: 250px;\n\n    overflow: auto;\n    border-left: 2px solid #bbb;\n    background: #f7f7f7;\n}\n\n#output {\n    border-top: 2px solid #bbb;\n    position: absolute;\n    left: 0;\n    bottom: 0;\n    height: 250px;\n    width: 100%;\n\n    background: white;\n    font-family: \"Courier New\";\n\n    z-index: 500;\n    overflow: hidden;\n}\n\n#output-inner {\n    overflow:auto;\n    height: 88%;\n}\n#output-inner div {\n    border-bottom: 1px solid #bbb;\n    padding: 3px;\n}\n#output-bar {\n    width: 100%;\n    border-top: 2px solid #aaaaaa;\n    position:absolute;\n    bottom: 0px;\n    background-color: white;\n}\n\n#output-bar.big-bar {\n    top: 50px;\n}\n\n.big-bar textarea {\n    min-height: 180px;\n}\n\n#eval {\n    position: absolute;\n    border: none;\n    right: 0px;\n    left: 50px;\n}\n#output-bar span {\n    color: #0040D0;\n    border: none;\n    font-weight: bolder;\n    line-height: 29px;\n    padding-right: 5px;\n}\n#showBig {\n    padding-left: 10px;\n    padding-right: 10px;\n    font-size: 16px;\n    cursor: pointer;\n}\n\n#btn-clear {\n    position: absolute;\n    right: 15px;\n}\n#btn-eval {\n    position: absolute;\n    right: 15px;\n    top: 55px;\n    z-index: 30;\n}\n\n#breakpoints {\n    list-style-type: none;\n    padding-left: 10px;\n}\n#breakpoints li {\n    border: 1px solid grey;\n    padding: 5px 10px;\n    border-radius: 6px;\n    -moz-border-radius: 6px;\n    -webkit-border-radius: 6px;\n    margin-bottom: 10px;\n    overflow: auto;\n}\n#breakpoints li span {\n    font-style: normal;\n    font-weight: normal;\n    font-size: 12px;\n    padding-left: 20px;\n}\n\n#breakpoints li span.breakpoint {\n    font-size: 14px;\n    font-style: italic;\n    font-weight: bold;\n    cursor: pointer;\n    padding-left: 0px;\n}\n#breakpoints li span.breakpoint:hover {\n    text-decoration: underline;\n}\n\n.unselectable {\n    -moz-user-select: -moz-none;\n    -khtml-user-select: none;\n    -webkit-user-select: none;\n\n    /*\n      Introduced in IE 10.\n      See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/\n    */\n    -ms-user-select: none;\n    user-select: none;\n}\n\n\n\n\n\n\n"
  },
  {
    "path": "ui/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>Aardwolf UI</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\n        <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.js\"></script>\n        <script type=\"text/javascript\" src=\"js/jstokenizer.js\"></script>\n        <script type=\"text/javascript\" src=\"js/coffeetokenizer.js\"></script>\n        <script type=\"text/javascript\" src=\"js/ui.js\"></script>\n\n        <link href=\"css/ui.css\" rel=\"stylesheet\" type=\"text/css\" />\n        <link href=\"css/buttons.css\" rel=\"stylesheet\" type=\"text/css\" />\n\t\t<link rel=\"stylesheet\" href=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/ui-lightness/jquery-ui.css\" type=\"text/css\" media=\"all\" />\n\n    </head>\n\n    <body>\n        <div id=\"top-toolbar\">\n            <button id=\"btn-continue\" class=\"button play green\" disabled>continue</button>\n            <span id=\"controls-coffeescript\">\n                <button id=\"btn-step\" class=\"button next green\" disabled>step</button>\n            </span>\n            <span id=\"controls-javascript\">\n                Step:\n                <button id=\"btn-step-over\" class=\"button step-over green\" disabled>over</button>\n                <button id=\"btn-step-in\" class=\"button step-in green\" disabled>in</button>\n                <button id=\"btn-step-out\" class=\"button step-out green\" disabled>out</button>\n            </span>\n\n            File:\n\n            <select id=\"file-switcher\">\n            </select>\n\n            <div id=\"options\">\n                <input type=\"checkbox\" id=\"clearConsoleConnect\" checked>Clear console on restart <br />\n                <input type=\"checkbox\" id=\"redirectConsole\">Redirect remote console here\n            </div>\n\n        </div>\n\n\n        <div id=\"center-panels\">\n            <div id=\"code-container\">\n                <div id=\"code\">&nbsp;</div>\n            </div>\n\n            <div id=\"sidebar\" class=\"\">\n                <h2>Breakpoints</h2>\n                <button id=\"btn-update-breakpoints\" class=\"button small\">update breakpoints</button>\n                <button id=\"btn-clear-breakpoints\" class=\"button small\">clear breakpoints</button>\n                <button id=\"btn-breakon-next\" class=\"button small\">break on next</button>\n                <ul id=\"breakpoints\"></ul>\n            </div>\n        </div>\n\n        <div id=\"output\">\n            <button id=\"btn-eval\" class=\"button small\">Execute</button>\n            <button id=\"btn-clear\" class=\"button small\">Clear console</button>\n            <div id=\"output-inner\"></div>\n            <div id=\"output-bar\"><span id=\"showBig\">&#8679;</span> <span id=\"prompt\">&gt;</span> <textarea id=\"eval\"></textarea></div>\n        </div>\n\n\n    </body>\n</html>\n\n"
  },
  {
    "path": "ui/js/coffeetokenizer.js",
    "content": "'use strict';\n\nvar keywordListCoffeeScript = [\n    'case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', \n    'native', '__hasProp', '__extends', '__slice', '__bind', '__indexOf', 'new', 'delete', 'typeof',\n    'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'if', 'else', 'switch',\n    'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'then', 'unless', 'until',\n    'loop', 'of', 'by', 'when', 'and', 'or', 'is', 'isnt', 'not', 'this', 'super'\n]; \n\nvar literalListCoffeScript = [\n    'true', 'false', 'null', 'undefined', 'yes', 'no', 'on', 'off'\n];\n\nfunction tokenizeCoffeeScript(str, onToken) {\n    var len = str.length;\n    var pos = 0;\n    var validRegexPos = false;\n    \n    while (pos < len) {\n        var c = str[pos];\n        \n        if (c === '\"' || c === \"'\") {\n            extractString(c);\n        }\n        else if (c === '#' && str[pos+1] === '#' && str[pos+2] === '#') {\n            extractMultiLineComment();\n        }\n        else if (c === '#') {\n            extractSingleLineComment();\n        }\n        else if (c === '/' && validRegexPos) {\n            extractRegexLiteral();\n        }\n        else if (c === ' ' || c === '\\t') {\n            extractWhitespace();\n        }\n        else if ('0123456789'.indexOf(c) > -1) {\n            extractNumber();\n        }\n        else if (c.match(/^[a-zA-Z_$]$/) !== null) {\n            extractWord();\n        }\n        else {\n            extractChar();\n        }\n    }\n    \n    function onTokenInternal(token, type) {\n        /* A slash following an assigment operator, a semicolon or an \n           opening paren can be a regex literal delimiter. */\n        if (type === 'char' && ':=;({'.indexOf(token) > -1) {\n            validRegexPos = true;\n        } else {\n            validRegexPos = false;\n        }\n        \n        onToken(token, type);\n    }\n    \n    function extractSingleLineComment() {\n        var endPos = str.indexOf(\"\\n\", pos);\n        if (endPos === -1) {\n            endPos = len - 1;\n        }\n        onTokenInternal(str.substring(pos, endPos), 'comment');\n        pos = endPos;\n    }\n    \n    function extractMultiLineComment() {\n        var endPos = pos;\n        while (!(str[++endPos] === '#' && str[endPos+1] === '#' && str[endPos+2] === '#'));\n        endPos += 3;\n        onTokenInternal(str.substring(pos, endPos), 'comment');\n        pos = endPos;\n    }\n    \n    function extractRegexLiteral() {\n        var endPos = pos;\n        /* regex literal body /.../ */\n        while (str[++endPos] != '/') {\n            if (str[endPos] == '\\\\') {\n                ++endPos;\n            }\n        }\n        /* flags following the body */\n        while ('gimy'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'regex');\n        pos = endPos;\n    }\n    \n    function extractString(quoteChar) {\n        var endPos = pos;\n        while (str[++endPos] != quoteChar) {\n            if (str[endPos] == '\\\\') {\n                ++endPos;\n            }\n        }\n        ++endPos;\n        onTokenInternal(str.substring(pos, endPos), 'string');\n        pos = endPos;\n    }\n    \n    function extractNumber() {\n        var endPos = pos;\n        while ('0123456789.eE'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'number');\n        pos = endPos;\n    }\n    \n    function extractWord() {\n        var endPos = pos;\n        while (str[++endPos].match(/^[a-zA-Z_$0-9]$/) !== null);\n        onTokenInternal(str.substring(pos, endPos), 'word');\n        pos = endPos;\n    }\n    \n    function extractWhitespace() {\n        var endPos = pos;\n        while (' \\t'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'whitespace');\n        pos = endPos;\n    }\n    \n    function extractChar() {\n        var c = str.substr(pos, 1);\n        onTokenInternal(c, c === '\\n' ? 'newline' : 'char');\n        ++pos;\n    }\n}\n"
  },
  {
    "path": "ui/js/jstokenizer.js",
    "content": "'use strict';\n\nvar keywordListJavaScript = [\n    'var', 'function', 'if', 'else', 'while', 'for', 'do', 'in', 'break', 'continue',\n    'switch', 'return', 'debugger', 'try', 'catch', 'throw', 'true', 'false', 'this'\n];\n\n\nvar literalListJavaScript = [\n    'true', 'false', 'null', 'undefined'\n];\n\n/* A simple JS tokenizer. We're really only interested in a couple of keywords, parentheses,\n   brackets and semicolons, so it doesn't need to be complete as long as it correctly handles\n   multi-word tokens such as strings and comments.\n*/\nfunction tokenizeJavaScript(str, onToken) {\n    var len = str.length;\n    var pos = 0;\n    var validRegexPos = false;\n\n    while (pos < len) {\n        var c = str[pos];\n\n        if (c === '\"' || c === \"'\") {\n            extractString(c);\n        }\n        else if (c === '/' && str[pos+1] === '/') {\n            extractSingleLineComment();\n        }\n        else if (c === '/' && str[pos+1] === '*') {\n            extractMultiLineComment();\n        }\n        else if (c === '/' && validRegexPos) {\n            extractRegexLiteral();\n        }\n        else if (c === ' ' || c === '\\t') {\n            extractWhitespace();\n        }\n        else if ('0123456789'.indexOf(c) > -1) {\n            extractNumber();\n        }\n        else if (c.match(/^[a-zA-Z_$]$/) !== null) {\n            extractWord();\n        }\n        else {\n            extractChar();\n        }\n    }\n\n    function onTokenInternal(token, type) {\n        /* A slash following an assigment operator, a semicolon or an\n           opening paren can be a regex literal delimiter. */\n        if (type === 'char' && ':=;({'.indexOf(token) > -1) {\n            validRegexPos = true;\n        } else {\n            validRegexPos = false;\n        }\n\n        onToken(token, type);\n    }\n\n    function extractSingleLineComment() {\n        var endPos = str.indexOf(\"\\n\", pos);\n        if (endPos === -1) {\n            endPos = len - 1;\n        }\n        onTokenInternal(str.substring(pos, endPos), 'comment');\n        pos = endPos;\n    }\n\n    function extractMultiLineComment() {\n        var endPos = pos;\n        while (!(str[++endPos] === '*' && str[endPos+1] === '/'));\n        endPos += 2;\n        onTokenInternal(str.substring(pos, endPos), 'comment');\n        pos = endPos;\n    }\n\n    function extractRegexLiteral() {\n        var endPos = pos;\n        /* regex literal body /.../ */\n        while (str[++endPos] != '/') {\n            if (str[endPos] == '\\\\') {\n                ++endPos;\n            }\n        }\n        /* flags following the body */\n        while ('gimy'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'regex');\n        pos = endPos;\n    }\n\n    function extractString(quoteChar) {\n        var endPos = pos;\n        while (str[++endPos] != quoteChar) {\n            if (str[endPos] == '\\\\') {\n                ++endPos;\n            }\n        }\n        ++endPos;\n        onTokenInternal(str.substring(pos, endPos), 'string');\n        pos = endPos;\n    }\n\n    function extractNumber() {\n        var endPos = pos;\n        while ('0123456789.eE'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'number');\n        pos = endPos;\n    }\n\n    function extractWord() {\n        var endPos = pos;\n        while (str[++endPos].match(/^[a-zA-Z_$0-9]$/) !== null);\n        onTokenInternal(str.substring(pos, endPos), 'word');\n        pos = endPos;\n    }\n\n    function extractWhitespace() {\n        var endPos = pos;\n        while (' \\t'.indexOf(str[++endPos]) !== -1);\n        onTokenInternal(str.substring(pos, endPos), 'whitespace');\n        pos = endPos;\n    }\n\n    function extractChar() {\n        var c = str.substr(pos, 1);\n        onTokenInternal(c, c === '\\n' ? 'newline' : 'char');\n        ++pos;\n    }\n}\n"
  },
  {
    "path": "ui/js/ui.js",
    "content": "'use strict';\n\nvar files = {};\nvar breakpoints = [];\nvar $codeContainer;\nvar $code;\n\nvar $continueBtn;\nvar $stepBtn;\nvar $stepOverBtn;\nvar $stepInBtn;\nvar $stepOutBtn;\n\nvar clearOnConnect = true;\nvar lineNum = 0;\nvar evalBig = false;\n\nvar history = [];\nvar currentHistoryPos = 0;\n\n$(function() {\n\t$('#sidebar').resizable({\n\t\thandles: 'w',\n\t\tstart: function() {\n\t\t\t$(document.body).addClass('unselectable');\n\t\t},\n\t\tstop: function(e, ui) {\n\t\t\tvar sidebar = $('#sidebar');\n\t\t\tsidebar.css('width', 'auto');\n\t\t\tsidebar.css('height', 'auto');\n\t\t\t$(document.body).removeClass('unselectable');\n\n\t\t\t$('#code-container').css('width', sidebar.position().left);\n        }\n\t});\n\n\t$('#output').resizable({\n\t\thandles: 'n',\n\t\tstart: function() {\n\t\t\t$(document.body).addClass('unselectable');\n\t\t},\n\t\tstop: function(e, ui) {\n\t\t\t$(document.body).removeClass('unselectable');\n\t\t\t$('#sidebar').css('bottom', $('#output').css('height'));\n\n\t\t\tvar code = $('#code-container'),\n\t\t\t\tyCode,\n\t\t\t\tyOutput,\n\t\t\t\tcodeHeight;\n\n\t\t\tyCode = code.position().top;\n\t\t\tyOutput = $('#output').position().top;\n\t\t\tcodeHeight = yOutput - yCode;\n\t\t\tcode.css('height', codeHeight);\n\t\t}\n\t});\n    $('#eval').val(\"\");\n\n\t$('#eval').keydown(function(e) {\n\t\te.stopPropagation();\n\n\t\tswitch (e.keyCode) {\n\t\t\tcase 13: // Enter\n\t\t\t\tif (!evalBig) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tif ($('#eval').val().match(/\\S/)) {\n\t\t\t\t\t\tevalCodeRemotely();\n\t\t\t\t\t\t$('#eval').val(\"\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 38: // Up\n\t\t\t\tif (!evalBig) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tif (currentHistoryPos < history.length) {\n\t\t\t\t\t\t$('#eval').val(history[currentHistoryPos++]);\n\n\t\t\t\t\t\tif (currentHistoryPos === history.length) {\n\t\t\t\t\t\t\tcurrentHistoryPos--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 40: // Down\n\t\t\t\tif (!evalBig) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tif (currentHistoryPos > 0) {\n\t\t\t\t\t\t$('#eval').val(history[--currentHistoryPos]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif ($('#eval').val() === history[currentHistoryPos]) {\n\t\t\t\t\t\t\t$('#eval').val('');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t});\n\n\t$('#btn-eval').hide();\n\n\t$('#btn-eval').click(function() {\n\t\tevalCodeRemotely();\n\t\thideBigEval();\n\t});\n\n\t$('#showBig').click(function() {\n\t\tif (evalBig) {\n\t\t\thideBigEval();\n\t\t} else {\n\t\t\tshowBigEval();\n\t\t}\n\t});\n\n    $('#btn-update-breakpoints').click(updateBreakpoints);\n\t$('#btn-clear-breakpoints').click(function() {\n\t\tbreakpoints = [];\n\t\tupdateBreakpoints();\n\t})\n    $('#btn-breakon-next').click(setBreakOnNext);\n    $('#btn-continue').click(breakpointContinue);\n    $('#btn-step').click(breakpointStep);\n    $('#btn-step-over').click(breakpointStepOver);\n    $('#btn-step-in').click(breakpointStepIn);\n    $('#btn-step-out').click(breakpointStepOut);\n    $('#file-switcher').change(switcherSwitchFile);\n\n\t$('#btn-clear').click(clearConsole);\n\n\t$('#clearConsoleConnect').change(function(e) {\n\t\tclearOnConnect = $(this).attr('checked') !== undefined;\n\t});\n\n\t$('#redirectConsole').change(toggleRedirectConsole);\n\n    $continueBtn = $('#btn-continue');\n    $stepBtn = $('#btn-step');\n    $stepOverBtn = $('#btn-step-over');\n    $stepInBtn = $('#btn-step-in');\n    $stepOutBtn = $('#btn-step-out');\n    $codeContainer = $('#code-container');\n    $code = $('#code');\n\n\t$(window).keydown(function(e) {\n\t\tswitch(e.which) {\n\t\t\tcase 80: // P - Continue\n\t\t\t\tif ($continueBtn.attr('disabled') == null) {\n\t\t\t\t\tbreakpointContinue();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 79: // O- Over\n\t\t\t\tif ($stepOverBtn.attr('disabled') == null) {\n\t\t\t\t\tbreakpointStepOver();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 78: // I- In\n\t\t\t\tif ($stepInBtn.attr('disabled') == null) {\n\t\t\t\t\tbreakpointStepIn();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 77: // U- Out\n\t\t\t\tif ($stepOutBtn.attr('disabled') == null) {\n\t\t\t\t\tbreakpointStepOut();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t});\n\n    loadSourceFiles();\n    listenToServer();\n\n    showFile({ file: $('#file-switcher').val() });\n\n\tif (window.localStorage) {\n\t\tvar strBreakpoints = window.localStorage.getItem('breakpoints');\n\t\tif (strBreakpoints) {\n\t\t\tbreakpoints = JSON.parse(strBreakpoints);\n\t\t\tupdateBreakpoints();\n\t\t}\n\t}\n});\n\nfunction initDebugger() {\n    loadSourceFiles();\n    postToServer({ command: 'set-breakpoints', data: breakpoints});\n}\n\nfunction loadSourceFiles() {\n\tvar prevSelected = $('#file-switcher').val();\n\n    var fileList = getFromServer('/files/list');\n    files = {};\n\n    $('#file-switcher option').remove();\n    addToFileSwitcher('', '<select file>');\n\n\tvar isAvailable = false;\n    fileList && fileList.files.forEach(function(f) {\n\t\tif (f === prevSelected) {\n\t\t\tisAvailable = true;\n\t\t}\n        var fdata = getFromServer('/files/data/' + f);\n        files[f] = {\n\t\t\tfile: fdata.data,\n\t\t\tbreakpoints: fdata.breakpoints\n\t\t};\n        addToFileSwitcher(f, f);\n    });\n\n\tif (isAvailable) {\n\t\t$('#file-switcher').val(prevSelected);\n\t}\n}\n\nfunction toggleRedirectConsole() {\n\tpostToServer({command: 'update-redirect-console', data: $('#redirectConsole').attr('checked') !== undefined});\n}\n\nfunction updateBreakpoints() {\n\tvar breakpoint,\n\t\tfileBreakpoints;\n\tfor (var i = 0; i < breakpoints.length; i++) {\n\t\tbreakpoint = breakpoints[i];\n\n\t\tif (!files[breakpoint[0]] || !files[breakpoint[0]].file) {\n\t\t\tbreakpoints.splice(i, 1);\n\t\t\ti--;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfileBreakpoints = files[breakpoint[0]].breakpoints;\n\t\tif (fileBreakpoints.indexOf(parseInt(breakpoint[1], 10)) < 0) {\n\t\t\tfor (var j = 0; j < fileBreakpoints.length; j++) {\n\t\t\t\tif (fileBreakpoints[j] > parseInt(breakpoint[1], 10)) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (j < fileBreakpoints.length) {\n\t\t\t\tbreakpoint[1] = fileBreakpoints[j];\n\t\t\t} else {\n\t\t\t\tbreakpoint[1] = fileBreakpoints[fileBreakpoints.length - 1];\n\t\t\t}\n\t\t}\n\t}\n\n\tif (window.localStorage) {\n\t\twindow.localStorage.setItem('breakpoints', JSON.stringify(breakpoints));\n\t}\n\n    postToServer({ command: 'set-breakpoints', data: breakpoints });\n    paintBreakpoints($('#file-switcher').val());\n\n\tvar ul = $('#breakpoints');\n\tul.empty();\n\tbreakpoints = breakpoints.sort(function(a, b) {\n\t\tif (a[0] > b[0]) {\n\t\t\treturn 1;\n\t\t} else if (a[0] < b[0]) {\n\t\t\treturn -1;\n\t\t} else {\n\t\t\treturn (a[1] - b[1]);\n\t\t}\n\t});\n\n\t$(breakpoints).each(function(i, breakpoint) {\n\t\tul.append(\n\t\t\t$('<li>')\n\t\t\t\t.append(\n\t\t\t\t\t$('<button>')\n\t\t\t\t\t\t.append('-')\n\t\t\t\t\t\t.click(function() {\n\t\t\t\t\t\t\tbreakpoints.splice(i, 1);\n\t\t\t\t\t\t\tupdateBreakpoints();\n\t\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t\t.append(\n\t\t\t\t\t$('<span>')\n\t\t\t\t\t\t.attr('class', 'breakpoint')\n\t\t\t\t\t\t.append(breakpoint[0] + ':' + breakpoint[1])\n\t\t\t\t\t\t.click(function() {\n\t\t\t\t\t\t\tshowFile({ file: breakpoint[0], goToLine: breakpoint[1]});\n\t\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t\t.append('<br>')\n\t\t\t\t//.append('&nbsp;&nbsp;&nbsp;&nbsp;')\n\t\t\t\t.append(\n\t\t\t\t\t$('<span>').append(getLine(files[breakpoint[0]].file, breakpoint[1]))\n\t\t\t\t)\n\t\t);\n\t})\n}\n\nfunction setBreakOnNext() {\n    postToServer({ command: 'break-on-next', data: breakpoints });\n}\n\nfunction evalCodeRemotely() {\n\tvar data =  $('#eval').val();\n\n\tdata = data.replace(/\\bthis\\b/, '__this');\n\thistory.unshift(data);\n\tcurrentHistoryPos = 0;\n    postToServer({ command: 'eval', data: data});\n}\n\nfunction breakpointContinue() {\n    removeLineHightlight();\n    disableContinueAndStep();\n    postToServer({ command: 'breakpoint-continue' });\n}\n\nfunction breakpointStepCommand(command) {\n    removeLineHightlight();\n    disableContinueAndStep();\n    postToServer({ command: command });\n}\n\nfunction breakpointStep(command) {\n    breakpointStepCommand('breakpoint-step');\n}\n\nfunction breakpointStepOver() {\n    breakpointStepCommand('breakpoint-step-over');\n}\n\nfunction breakpointStepIn() {\n    breakpointStepCommand('breakpoint-step-in');\n}\n\nfunction breakpointStepOut() {\n    breakpointStepCommand('breakpoint-step-out');\n}\n\nfunction addToFileSwitcher(filePath, fileLabel) {\n    $('<option></option>').val(filePath).text(fileLabel).appendTo($('#file-switcher'));\n}\n\nfunction switcherSwitchFile() {\n    showFile({ file: $('#file-switcher').val() });\n}\n\nfunction postToServer(payload) {\n    var req = new XMLHttpRequest();\n    req.open('POST', '/desktop/outgoing', false);\n    req.setRequestHeader('Content-Type', 'application/json');\n    req.send(JSON.stringify(payload));\n}\n\nfunction getFromServer(path) {\n    var req = new XMLHttpRequest();\n    req.open('GET', path, false);\n    req.send();\n    return safeJSONParse(req.responseText);\n}\n\nfunction listenToServer() {\n    var req = new XMLHttpRequest();\n    req.open('GET', '/desktop/incoming', true);\n    req.onreadystatechange = function () {\n        if (req.readyState == 4) {\n            var data = safeJSONParse(req.responseText);\n            if (data) {\n                processOutput(data);\n            }\n            listenToServer();\n        }\n    };\n    req.send(null);\n}\n\nfunction showBreakpoint(data) {\n    showFile(data);\n    $('#file-switcher').val(data.file);\n}\n\nfunction showFile(data) {\n    var codeTokens = [];\n    var keywordList;\n    var literalList;\n    var tokenize;\n\n    if (fileExt(data.file) == 'coffee') {\n        keywordList = keywordListCoffeeScript;\n        literalList = literalListCoffeScript;\n        tokenize = tokenizeCoffeeScript;\n\n        $('#controls-coffeescript').show();\n        $('#controls-javascript').hide();\n    }\n    else {\n        keywordList = keywordListJavaScript;\n        literalList = literalListJavaScript;\n        tokenize = tokenizeJavaScript;\n\n        $('#controls-coffeescript').hide();\n        $('#controls-javascript').show();\n    }\n\tif (!files[data.file]) {\n\t\treturn;\n\t}\n    tokenize(files[data.file].file || '', function(token, type) {\n        var pre = '';\n        var post = '';\n\n        if (type === 'word' && keywordList.indexOf(token) > -1) {\n            pre = '<span class=\"keyword\">';\n            post = '</span>';\n        }\n        else if (type === 'word' && literalList.indexOf(token) > -1) {\n            pre = '<span class=\"literal\">';\n            post = '</span>';\n        }\n        else if (['string', 'comment', 'number'].indexOf(type) > -1) {\n            pre = '<span class=\"'+type+'\">';\n            post = '</span>';\n        }\n\n        codeTokens.push(pre);\n        codeTokens.push(token.replace(/</g, '&lt;'));\n        codeTokens.push(post);\n    });\n\n    var codeLines = codeTokens\n        .join('')\n        .split('\\n')\n        .map(function(x, i) {\n            var num = String(i+1);\n\n\t\t\tvar extraClass = '';\n\t\t\tif (files[data.file].breakpoints.indexOf(i + 1) < 0) {\n\t\t\t\textraClass = ' no-breakpoint';\n\t\t\t}\n            var paddedNum = '<span class=\"linenum'+ extraClass + '\" file=\"'+data.file+'\" line=\"'+num+'\">' +\n                            '      '.substr(num.length) + num + ' ' +\n                            '</span>';\n            return paddedNum + ' ' + x;\n        });\n\n    $code.html(codeLines.join('\\n'));\n    $code.find('.linenum:not(.no-breakpoint)').click(toggleBreakpoint);\n    paintBreakpoints(data.file);\n\n    var numLines = codeLines.length;\n    var textAreaHeight = $codeContainer.height();\n    var textAreaContentHeight = $codeContainer[0].scrollHeight;\n    var codeHeight = $code.height();\n    var heightPerLine = codeHeight / numLines;\n\n\tvar line = 0;\n    if (data.line) {\n\t\tline = data.line;\n        highlightLine(data.line, numLines);\n        enableContinueAndStep();\n    } else if (data.goToLine) {\n\t\tline = data.goToLine;\n\t}\n\n    if (textAreaContentHeight > textAreaHeight) {\n        var scrollAmountPerLine = (textAreaContentHeight - textAreaHeight) / numLines;\n        var scrollTo = Math.round(line * scrollAmountPerLine);\n        $codeContainer.scrollTop(scrollTo);\n    }\n}\n\nfunction highlightLine(line, numLines) {\n    var codeHeight = $code.height();\n    var heightPerLine = codeHeight / numLines;\n\n    $code.css({\n        'background-image': 'url(\"img/breakpoint-arrow.png\"), url(\"img/breakpoint-bg.png\")',\n        'background-repeat': 'no-repeat, no-repeat, repeat-y',\n        'background-size': '9px 7px, 100% '+Math.round(heightPerLine)+'px',\n        'background-position': '5px '+Math.round((line - 1) * heightPerLine + ((heightPerLine - 7) / 2))+'px, '+\n                               '0px '+Math.round((line - 1) * heightPerLine)+'px'\n    });\n}\n\nfunction removeLineHightlight() {\n    $code.css({ 'background-image': '' });\n}\n\nfunction paintBreakpoints(file) {\n    $code.find('.linenum').each(function(i, elem) {\n        if (existsBreakpoint(file, i+1)) {\n            $(elem).addClass('breakpoint');\n        }\n        else {\n            $(elem).removeClass('breakpoint');\n        }\n    });\n}\n\nfunction existsBreakpoint(f, l) {\n    return breakpoints.filter(function(b) { return b[0] == f && b[1] == l; }).length > 0;\n}\n\nfunction toggleBreakpoint() {\n    if (breakpoints === null) {\n        alert('Could not parse the list of breakpoints!');\n        return;\n    }\n\n    var $marker = $(this);\n    var file = $marker.attr('file');\n    var line = $marker.attr('line');\n    if (existsBreakpoint(file, line)) {\n        $(this).removeClass('breakpoint');\n\n        breakpoints = breakpoints.filter(function(b) { return !(b[0] == file && b[1] == line); });\n    }\n    else {\n        $(this).addClass('breakpoint');\n\n        breakpoints.push([file, line]);\n    }\n\n    updateBreakpoints();\n}\n\nfunction enableContinueAndStep() {\n    $continueBtn.attr('disabled', null);\n    $stepBtn.attr('disabled', null);\n    $stepOverBtn.attr('disabled', null);\n    $stepInBtn.attr('disabled', null);\n    $stepOutBtn.attr('disabled', null);\n}\n\nfunction disableContinueAndStep() {\n    $continueBtn.attr('disabled', true);\n    $stepBtn.attr('disabled', true);\n    $stepOverBtn.attr('disabled', true);\n    $stepInBtn.attr('disabled', true);\n    $stepOutBtn.attr('disabled', true);\n}\n\nfunction processOutput(data) {\n    switch (data.command) {\n        case 'mobile-connected':\n\t\t\tif (clearOnConnect) {\n\t\t\t\tclearConsole();\n\t\t\t}\n\t\t\t$('#redirectConsole').attr('checked', false);\n            writeToConsole('Remote device connected.');\n            initDebugger();\n            break;\n        case 'print-message':\n            writeToConsole('<b>'+data.type + '</b>: ' + data.message);\n            break;\n        case 'print-eval-result':\n\t\t\ttry {\n\t\t\t\twriteToConsole(\n\t\t\t\t\t'<br/>&nbsp;&nbsp;&nbsp; <b>INPUT:</b> ' + data.input.replace(/\\b__this\\b/, 'this') +\n\t\t\t\t\t\t'<br/>&nbsp;&nbsp;&nbsp; <b>RESULT:</b> ' +\n\t\t\t\t\t\tJSON.stringify(JSON.parse(data.result), null, '\\t')\n\t\t\t\t\t\t\t.replace(/\\n/g, '<br>&nbsp;&nbsp;&nbsp;')\n\t\t\t\t\t\t\t.replace(/\\t/g, '&nbsp;&nbsp;'));\n\t\t\t} catch (e) {\n           \t\twriteToConsole('<b>INPUT:</b> ' + data.input.replace(/\\b__this\\b/, 'this') + ' <b>RESULT:</b> ' + data.result);\n\t\t\t}\n            break;\n        case 'report-exception':\n            writeToConsole('<b>EXCEPTION</b>: ' + data.message + ' at ' + data.file + ', line ' + data.line);\n            break;\n        case 'report-breakpoint':\n            showBreakpoint(data);\n            break;\n    }\n}\n\nfunction safeJSONParse(str) {\n    try {\n        return JSON.parse(str);\n    } catch (ex) {\n        return null;\n    }\n}\n\nfunction clearConsole() {\n\tlineNum = 0;\n\t$('#output-inner').empty();\n}\n\nfunction writeToConsole(msg) {\n    $('<div></div>').html((++lineNum) + ': ' + msg).appendTo($('#output-inner'))[0].scrollIntoView();\n}\n\n\nfunction fileExt(fileName) {\n    return fileName.split('.').slice(-1)[0];\n}\n\nfunction showBigEval() {\n\tevalBig = true;\n\t$('#showBig').html('&#8681;');\n\t$('#output-bar').addClass('big-bar');\n\t$('#btn-eval').show();\n}\nfunction hideBigEval() {\n\tevalBig = false;\n\t$('#showBig').html('&#8679;');\n\t$('#output-bar').removeClass('big-bar');\n\t$('#eval').val('');\n\t$('#btn-eval').hide();\n}\n\nfunction getLine(string, lineNo) {\n\tvar newString = string;\n\tfor (var i = 0; i < lineNo - 1; i++) {\n\t\tnewString = newString.slice(newString.indexOf('\\n') + 1);\n\t}\n\treturn newString.substring(0, newString.indexOf('\\n'));\n}\n"
  }
]