/* * console.js - Firebug console emulator * 依存するライブラリ: * event.js, window.js, style.js queryselector.js */ function() { var bind = function(obj, fn) { return function() { return fn.apply(obj, arguments); } }; var fixEvent = @INCLUDE(event.js); var parse = function(str) { if (typeof str != 'string') return true; var stack = new Array(); stack.match = function(ch, expect) { /*if (this.length == 0) { return false; } else */ if (this.length > 0 && expect == this[this.length - 1]) { this.pop(); return true; } else { this.push(ch); return false; } }; for (var i = 0; i < str.length; i++) { switch(str.charAt(i)) { case '\\': break; case "'": stack.match("'", "'"); break; case '"': stack.match('"', '"'); break; case '(': stack.push('('); break; case ')': stack.match(')', '('); break; case '[': stack.push('['); break; case ']': stack.match(']', '['); break; case '{': stack.push('{'); break; case '}': stack.match('}', '{'); break; } } if (stack.length == 0) return true; else return false; }; /* properties */ this.title = 'console'; this.width = 400; this.height= 300; this.content = document.createElement('ul'); this.content.className = this.content.styleClass = 'console'; this.window = new @INCLUDE(window.js)(this); /* style */ this.style = (@INCLUDE(style.js))( "ul.console {" + " font-family:monospace;" + " word-wrap:break-word;" + " list-style-type:none;" + " margin:0;" + " padding:0;" + " text-align:left;" + " color:black;" + "}" + "ul.console li.prompt span {" + " outline:none;" + "}" ); /* history */ this.history = new Array(); this.history.mark = 0; this.history.add = function(src) { this.push(src); this.clearMark(); }; this.history.clearMark = function() { this.mark = this.length; }; this.history.prev = function() { if (this.length == 0) { return; } else if (this.mark > 0) { return this[--this.mark]; } else { this.mark = 0; return this[0]; } }; this.history.next = function() { if (this.length == 0) { return; } else if (this.mark == this.length - 1) { return this[this.mark]; } else if (this.mark < this.length) { return this[++this.mark]; } else { this.mark = this.length - 1; return this[this.mark]; } }; this.history.toString = function() { var result = []; for (var i = 0; i < this.length; i++) result.push((i+1) + '. ' + this[i]); return result.join("\n"); }; /* functions */ this.onunload = bind(this, function() { this.style.parentNode.removeChild(this.style); }); this.clear = bind(this, function() { this.content.innerHTML = ''; this.prompt(); }); this.log = bind(this, function(msg) { var line = this.content.appendChild(document.createElement('li')); var lines = typeof msg == 'string' ? msg.split("\n") : [msg]; for (var i = 0; i < lines.length; i++) { line.appendChild(document.createTextNode(lines[i])); line.appendChild(document.createElement('br')); } return msg; }); this.onmouseover = function(evt) { evt = fixEvent(evt); evt.preventDefault(); evt.stopPropagation(); if (this.commandLine) this.commandLine.focus(); return false; }; this.prompt = bind(this, function() { var line = this.content.appendChild(document.createElement('li')); line.className = line.styleClass = 'prompt'; var prefix = line.appendChild(document.createElement('span')); prefix.innerHTML = 'js>'; var cmd = this.commandLine = line.appendChild(document.createElement('span')); cmd.console = this; cmd.contentEditable = true; cmd.onkeydown = bind(cmd, function(evt) { evt = evt ? evt : window.event; var console = this.console; var src = this.textContent || this.innerText; switch (evt.keyCode) { case 13: //\n if (src && !parse(src)) { var br = this.appendChild(document.createElement('br')); var empty = this.appendChild(document.createElement('span')); empty.appendChild(document.createTextNode("\n ")); empty.style.height = '1em'; empty.style.width = '0'; this.moveLast(); return true; } else if (src) { this.contentEditable = false; console.log(this.console.execute(src.replace(/\n>/g, "\n"))); console.prompt(); console.history.add(src); return false; } else { this.contentEditable = false; console.prompt(); console.history.clearMark(); return false; } break; case 27: //ESC console.clear(); break; case 38: //UP ARROW var prev = console.history.prev(); if (prev) this.innerHTML = prev; break; case 40: //DOWN ARROW var next = console.history.next(); if (next) this.innerHTML = next; break; } return true; }); cmd.moveLast = function() { window.getSelection().selectAllChildren(this); var range = window.getSelection().getRangeAt(0); range.setStart(range.endContainer, range.endOffset); }; cmd.focus(); }); this.execute = bind(this, function(src) { try { if (src == null || src.match(/^\s*$/)) return ""; var result = this.scope.exec(src); switch(typeof result) { case 'function': return 'function'; case 'string' : case 'undefined': case 'null' : return result; default: if (result && typeof result.toString === 'function') return result.toString(); else return result; } } catch (e) { return e + ":" + src; } }); this.dir = bind(this, function(obj) { var result; if (typeof(obj) == 'object' && typeof(obj.length) == 'number' && typeof(obj.shift) == 'function') { //maybe array var str = "["; var sep = ""; for (var i = 0; i < obj.length; i++) { str += sep + obj[i]; sep = ", "; } str += "]"; result = str; } else if (typeof(obj) == 'object') { //object var str = "{"; var keys = []; for (var key in obj) { keys.push(key); } keys = keys.sort(); var sep = ""; for (var i = 0; i < keys.length; i++) { if (!keys[i]) continue; try { var val = obj[keys[i]]; if (val) str += sep + keys[i] + "=" + val; }catch(e){} sep = ","; } str += "}"; result = str; } else if (typeof(obj) == 'function') { result = obj.toString(); } else if (typeof(obj) == 'string') { result = obj; } else { result = obj; } self.log(result); }); /* scope */ var self = this; this.scope = new function() { var version = '2011-07-27'; var help = [ "* スクリプト+ENTERで式を評価", "* ESCで画面クリア\n* ↑↓で履歴を見る", "* help .. このヘルプを表示する", "* version .. バージョン", "* history .. 履歴の一覧", "* $(query) .. セレクタqueryに一致する要素を取り出す", "* log(msg) .. msgを出力する", "* dir(obj) .. objを調べる", "* exit() .. この画面を終了する" ].join("\n"); var $ = @INCLUDE(queryselector.js); var log = self.log; var dir = self.dir; var history = self.history; var grep = function(pattern, obj) { var result = []; var expr = new RegExp(pattern, 'i'); for (var key in obj) { if (key.match(expr)) result.push(key); } return result.join("\n"); }; var exit = bind(self.window, self.window.exit); this.exec = bind(this, function(src) { return eval(src); }); }; this.prompt(); }