//============================================================================================
// Author:			Aaron Graham
// Version:			1.2
// Description:		A cross-browser Javascript debugging tool. 
// Compatibility:	Tested on 
// 					(Windows)	IE 6.0, Opera 8.0+, Mozilla Firefox, Netscape 7.0+
// Change Log:
// 	1-25-2006
// 		- I have finally succeded in adding basic Opera support. Unfortunately Opera doesn't detect
// 		  window unload events, so it can't store window geometry when you close it, but that's a minor inconvenience.
// TODO:
// Need to figure out new key combo to launch debugger. Current setup makes Opera
// open two windows, the debugger, and another URL which contains the contents of the clipboard (weird)
//============================================================================================
var __DBG_STATE			= new Object();			// an object to store the debugger window's geometry and state
var __DBG_WINDOW 		= null;					// a reference to the debugger window
var __DBG_CMD_HIST 		= new Array(); 			// a history of debugger commands
var __DBG_CMD_HIST_PTR	= 0; 					// array index pointer for command history entries
var __DBG_VERSION		= 1.2;					// the version of the debugger
var __cDEBUG_OUTPUT 	= ""; 					// a record of the debugger's output
var __rxCLEAR			= /^CLEAR$/i;			// regex to match the 'clear' keyword
var __rxHISTORY			= /^HISTORY$/i;			// regex to match the 'history' keyword
var __rxEXIT			= /^EXIT$/i;			// regex to match the 'exit' keyword

// attempt to get window geometry and debugger state from the cookie, otherwise set it to default values
if(!dbg_restore_dbg_state()){ dbg_set_default_dbg_state(); }

if(__DBG_STATE.win_open == "1"){ req_debug_window(); }

// If you have a script debugger installed, you might need to disable script debugging 
// in Internet Explorer (Tools, Internet Options, Advanced) before the onerror event will fire.
window.onerror = dbg_win_onerror;
window.onunload	= dbg_app_onunload;
window.document.onkeypress = dbg_app_onkeypress;
//============================================================================================
function req_debug_window(){
	// create our window based on the geometry that was found (or set by default)
	with(__DBG_STATE){
		var nWin_x = (win_x != "0") ? win_x : (body_x - win_left_pad);
		var nWin_y = (win_y != "0") ? win_y : (body_y - win_top_pad);
	}
	var cFeatures = 
		'top=' + ((nWin_y >= 0) ? nWin_y : 0) + ',' +
		'left=' + ((nWin_x >= 0) ? nWin_x : 0) + ',' +
		'width=' + __DBG_STATE.body_width + ',' +
		'height=' + __DBG_STATE.body_height + ',' +
		'scrollbars=no,' +
		'resizable=yes,' +
		'location=no,' +
		'menubar=no,' +
		'status=no,' +
		'titlebar=no,' +
		'toolbar=no';

	// the debugger will only open if the browser hasn't blocked popup windows
	__DBG_WINDOW = window.self.open('', '_blank', cFeatures);

	// NS and Moz take some time to recognize the existence of a new window. Hence the setTimeout
	setTimeout('dbg_new_debugger()', 250);

	// if the window has negative position coordinates, move it into place after
	// creation. window.open doesn't like to create a window that's not completely on the screen
	if(nWin_x < 0 || nWin_y < 0){
		setTimeout('__DBG_WINDOW.moveTo('+nWin_x+','+nWin_y+');', 250);
	}
}
//============================================================================================
function dbg_new_debugger(){
	var op = '';

	// define the html necessary to render the debugger window
	op += '<body id="dbg_body" bgcolor="#808080" style="';
	op += 	'margin:0px;';
	op += 	'padding:0px;';
	op += 	'background-color:#808080;';
	op += '">';
	op += '<div id="dbg_output_div" style="';
	op += 	'width:100%;';
	op += 	'height:100px;';
	op += 	'background-color:#000000;';
	op += 	'color:#00ff00;';
	op += 	'border:0px;';
	op += 	'font:normal normal normal 12px \'Lucida Console\',\'Courier New\',Courier,Terminal;';
	op += 	'line-height:12px;';
	op += 	'margin:0px;';
	op += 	'padding:0px;';
	op += 	'overflow:auto;';
	op += 	'white-space: nowrap;';
	op += '">';
	op += '</div>';
	op += '<input id="dbg_cmd_input" type="text" value="" style="';
	op += 	'width:100%;';
	op += 	'height:16px;';
	op += 	'border:1px solid #000000;;';
	op += 	'font:normal normal normal 12px \'Lucida Console\',\'Courier New\',Courier,Terminal;';
	op += 	'line-height:12px;';
	op += 	'margin:0px;';
	op += 	'padding:0px;';
	op += '">';
	op += '</body>';

	// write the debugger's html to the popup window
	__DBG_WINDOW.document.open();
	__DBG_WINDOW.document.write(op);
	__DBG_WINDOW.document.close();

	// give the debugger window attributes to quickly reference it's major DOM objects.
	__DBG_WINDOW.__dDebugDiv 		= __DBG_WINDOW.document.getElementById('dbg_output_div');
	__DBG_WINDOW.__dCmdTextInput 	= __DBG_WINDOW.document.getElementById('dbg_cmd_input');

	// display any debugger output that's been stored
	__DBG_WINDOW.__dDebugDiv.innerHTML = __cDEBUG_OUTPUT;

	// initialize the debugger window geometry
	dbg_win_onresize();

	// set our window's event handlers
	__DBG_WINDOW.onresize 						= function(){ dbg_win_onresize(); }; //Detect Window Resizing in NS and Moz
	__DBG_WINDOW.document.body.onresize 		= function(){ dbg_win_onresize(); }; //Detect Window Resizing in IE
	__DBG_WINDOW.document.onkeyup				= function(e){ dbg_doc_onkeyup(e ? e : __DBG_WINDOW.event); };
	__DBG_WINDOW.document.body.onbeforeunload 	= dbg_win_onunload; // detect window closing in IE
	__DBG_WINDOW.onunload 						= dbg_win_onunload; // detect window closing in Moz and NS 

	// focus the debuggers command text input 
	// we have to give it some time to render, resize, hide-n-display
	setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 250);

	// focus our app (not the debugger)
	window.focus();

	// we can figure out the window's left border width (win_left_pad) and titlebar
	// height (win_top_pad) because we know that the window is at (0,0) (needed for IE only)
	if(__DBG_STATE.real_geometry == "0"){
		__DBG_STATE.win_left_pad = (__DBG_WINDOW.screenLeft) ? (__DBG_WINDOW.screenLeft) : 0;
		__DBG_STATE.win_top_pad = (__DBG_WINDOW.screenTop) ? (__DBG_WINDOW.screenTop) : 0;
	}

	// set the debugger window's title
	__DBG_WINDOW.document.title = 'JavaScript Debugger v'+__DBG_VERSION;
}
//============================================================================================
function dbg_capture_dbg_state(){
	var s = __DBG_STATE;
	var w = __DBG_WINDOW;
	s.win_x			= (w.screenX) ? (w.screenX) : 0;
	s.win_y			= (w.screenY) ? (w.screenY) : 0;
	s.body_x 		= (w.screenLeft) ? (w.screenLeft) : 0;
	s.body_y 		= (w.screenTop) ? (w.screenTop) : 0;
	s.body_width 	= w.document.body.clientWidth;
	s.body_height 	= w.document.body.clientHeight;
	s.real_geometry = 1;
	s.cmd_history = __DBG_CMD_HIST.join("###");
}
//============================================================================================
function dbg_store_dbg_state(){
	var s = __DBG_STATE;
	var geometry_cookie = 'dbg_geometry_state=' +
		'win_left_pad:' + s['win_left_pad'] + '&' +
		'win_top_pad:' 	+ s['win_top_pad'] + '&' +
		'win_x:' 		+ s['win_x'] + '&' +
		'win_y:' 		+ s['win_y'] + '&' +
		'body_x:' 		+ s['body_x'] + '&' +
		'body_y:' 		+ s['body_y'] + '&' +
		'body_width:' 	+ s['body_width'] + '&' +
		'body_height:' 	+ s['body_height'] + '&' +
		'real_geometry:'+ s['real_geometry'] + '&' +
		'win_open:'		+ s['win_open'] + '&' +
		'cmd_history:'	+ s['cmd_history'] + ';';
	// save the cookie by writing to the magic document.cookie property
	document.cookie = geometry_cookie;
}
//============================================================================================
function dbg_restore_dbg_state(){
	var sCookieName = 'dbg_geometry_state';	// the name of the cookie that contains our window's geometry state
	var sCookieVal = "";					// the value of the cookie
	var jGeometry = new Object();			// an object to store the parsed debugger window geometry
	var nStartIdx, nEndIdx;	 				// character indexes to help parse out the cookie
	var i = 0;  							// a counter variable
	var tmp;  								// a temporary variable to aid in the string parsing
	var bReturnVal = false;					// the success code returned by the function

	if(document.cookie != ""){  // make sure that the document contains cookie(s)
		// Now extract just the value of our geometry cookie from that 
		// list by finding the start/end character indexes of the cookie value.
		nStartIdx = document.cookie.indexOf(sCookieName + '=');
		if(nStartIdx != -1){ 
			nStartIdx += sCookieName.length + 1; // adjust to start index to ignore the cookie name and equals sign
			nEndIdx = document.cookie.indexOf(';', nStartIdx);
			if(nEndIdx == -1) nEndIdx = document.cookie.length; // no semicolon implies the cookie is at the end of the list
			sCookieVal = document.cookie.substring(nStartIdx, nEndIdx);

			// place the name/value pairs stored in the sCookieVal string into the jGeometry object where the name value
			// pairs coorespond to attributes and values of the jGeometry object
			tmp = sCookieVal.split('&');	
			for(i=0; i < tmp.length; i++){ 
				tmp[i] = tmp[i].split(':'); 
				jGeometry[tmp[i][0]] = unescape(tmp[i][1]);
			}
			bReturnVal = true; // return success
			__DBG_STATE = jGeometry; // set our global geometry object
			dbg_restore_cmd_hist(); // restore any command history
		}
	}
	return bReturnVal;
}
//============================================================================================
function dbg_restore_cmd_hist(){
	// see if any commands were restored from cookie to our debugger state
	if(__DBG_STATE.cmd_history.length > 0){
		// parse out the commands from state string into command history array
		__DBG_CMD_HIST = __DBG_STATE.cmd_history.split("###");

		// set the history pointer to the end of the command array
		__DBG_CMD_HIST_PTR  = __DBG_CMD_HIST.length;
	}
}
//============================================================================================
function dbg_set_default_dbg_state(){
	var s = __DBG_STATE;
	s.win_left_pad 	= 0; // not true, but we figure this out later in IE
	s.win_top_pad 	= 0; // not true, but we figure this out later in IE
	s.win_x			= 0; // used only by NS/MOZ
	s.win_y			= 0; // used only by NS/MOZ
	s.body_x 		= 0; // used only by IE in conjunction with win_left_padding
	s.body_y 		= 0; // used only by IE in conjunction with win_top_padding
	s.body_width 	= 400;
	s.body_height 	= 300;
	s.real_geometry = 0; // flags that these are default geometry values
	s.win_open		= 0; // flags that the debugger window should be hidden by default
	s.cmd_history	= "";// no command history yet
}
//============================================================================================
function dbg_quick_sort_array(_aInputArray, _nLowIdx, _nHighIdx, _fCompareFunction){
	var i, j, dTempVal;

	while(_nHighIdx > _nLowIdx){
		i = _nLowIdx;
		j = _nHighIdx;
		dTempVal = _aInputArray[_nLowIdx];
		while(i < j){
			while(_fCompareFunction(_aInputArray[j], dTempVal) > 0){ j -= 1; }
			_aInputArray[i] = _aInputArray[j];
			while((i < j) && (_fCompareFunction(_aInputArray[i], dTempVal) <= 0)){ i++; } 
			_aInputArray[j] = _aInputArray[i];
		}
		_aInputArray[i] = dTempVal;
		if(i - _nLowIdx < _nHighIdx - i){
			dbg_quick_sort_array(_aInputArray, _nLowIdx, i-1, _fCompareFunction); 
			_nLowIdx = i + 1;
		}
		else{
			dbg_quick_sort_array(_aInputArray, i+1, _nHighIdx, _fCompareFunction); 
			_nHighIdx = i - 1;
		}
	}

	return _aInputArray;
}
//============================================================================================
function dbg_compare_by_property(_jObjA, _jObjB){
	var _cValA = _jObjA[0].toLowerCase();
	var _cValB = _jObjB[0].toLowerCase();
	return (_cValA == _cValB) ? 0 : (_cValA > _cValB) ? 1 : -1;
}
//============================================================================================
function dbg_spaces(_nSpaces){
	var cSpaces = '';
	for(var i = 0; i < _nSpaces; i++){ cSpaces += ' '; }
	return cSpaces;
}
//============================================================================================
function dbg_tab_complete(){
	// first look to see what's in typed in the text input
	var cCmd = __DBG_WINDOW.__dCmdTextInput.value;
	var acCandidates = new Array();
	var tmp = new Array();
	var bSearching = true;
	var i = 0;
	var j = 0;
	if(cCmd != ''){
		for(i = 0; i < __DBG_CMD_HIST.length; i++){ // look at all commands in the history
			// see if any commands in the history start with what's typed
			// store matches in the tmp object for further analysis
			if(__DBG_CMD_HIST[i].indexOf(cCmd,0) == 0){ tmp.push(__DBG_CMD_HIST[i]); }
		}

		if(tmp.length > 0){
			if(tmp.length == 1){ __DBG_WINDOW.__dCmdTextInput.value = tmp[0]; }
			// sort the candidates array if there's more than one
			else{ 
				// sorting is a necessary step in removing duplicates
				tmp.sort(); 

				// remove duplicate entries
				acCandidates.push(tmp.pop());
				while(1){
					// continue until we've popped away all of the tmp array's data
					if(tmp.length == 0){ break; }
					// if the tail of tmp equals the tail of acCandidates, it's a duplicate, and can be popped into oblivion
					else if(tmp[tmp.length - 1] == acCandidates[acCandidates.length - 1]){ tmp.pop(); }
					// otherwise, it's unique and belongs in acCandidates
					else{ acCandidates.push(tmp.pop()); }
				}	
				
				// one candidate means we've found our tab completion target
				if(acCandidates.length == 1){ __DBG_WINDOW.__dCmdTextInput.value = acCandidates[0]; }
				// do a character by character search to determine how far we can tab complete
				else{
					j = cCmd.length;
					while(bSearching){
						for(i = 1; i < acCandidates.length; i++){
							if(j >= acCandidates[i].length || j >= acCandidates[i-1].length || acCandidates[i].charAt(j) != acCandidates[i-1].charAt(j)){ 
								bSearching = false; 
								break;
							}
						}
						if(bSearching){ j++; }
					}

					// update the text input with the tab completion
					__DBG_WINDOW.__dCmdTextInput.value = acCandidates[0].substr(0,j);
				}
			}		
		}
	}
}
//============================================================================================
function dbg_get_prop_vals(_jObj){
	var aPropVals = new Array(); // an array to store results
	var cAttrib, mVal; // variables for the object's Attributes and their Values

	for(cAttrib in _jObj){
		// first, see if the client script is allowed to access the variable
		try{ mVal = _jObj[cAttrib]; }
		catch(jError){ mVal = '### BROWSER PROTECTED VALUE ###'; }

		// next, see if the value can be converted to a string (some of Opera's objects can't be (like the sun,java,Packages,etc.)
		try{ mVal += ' '; } // implicite string conversion
		catch(e){ mVal = "### UNKNOWN VALUE (STRING CONVERSION FAILED) ###"; }

		// push the name/value pair onto our array
		aPropVals.push(new Array(cAttrib, mVal));
	}
	return aPropVals;
}
//============================================================================================
function dbg_format_prop_vals(_aPropVals){
	var cOutput = '';
	var i = 0;
	var nMaxPropLen = 0; // the length in characters of the longest property string
	if(arguments.length > 0){
		// look for the longest property string
		for(i in _aPropVals){
			if(_aPropVals[i][0].length > nMaxPropLen){ nMaxPropLen = _aPropVals[i][0].length; }
		}
	
		// sort our array by property name
		_aPropVals = dbg_quick_sort_array(_aPropVals, 0, _aPropVals.length-1, dbg_compare_by_property);
	
		for(i = 0; i < _aPropVals.length; i++){
			// ignore detection on null, undefined, and blank values
			if(_aPropVals[i][1] != null && _aPropVals[i][1] != void(0) && _aPropVals[i][1] != ''){
				// strip away newlines and carriage returns so
				// each attribute fits on a single line (not the case for echo()
				_aPropVals[i][1] = _aPropVals[i][1].replace(/\n/g, "");
				_aPropVals[i][1] = _aPropVals[i][1].replace(/\r/g, "");
			}
			cOutput += _aPropVals[i][0] + dbg_spaces(nMaxPropLen - _aPropVals[i][0].length) + ' = ' + _aPropVals[i][1] + "\n";
		}
	}
	return cOutput;
}
//============================================================================================
function dbg_scroll_to_bottom(){
	__DBG_WINDOW.__dDebugDiv.scrollTop = __DBG_WINDOW.__dDebugDiv.scrollHeight;
}
//============================================================================================
function dbg_append_output(_cDebugMsg){
	// update the debugger's output
	__cDEBUG_OUTPUT += _cDebugMsg;

	// check to see if the debugger window is closed
	if(__DBG_WINDOW != null){
		if(__DBG_WINDOW.closed == false){
			if(__DBG_WINDOW.__dDebugDiv){ 
				// if window exists and is fully rendered, update the output
				__DBG_WINDOW.__dDebugDiv.innerHTML += _cDebugMsg; 
				// scroll to bottom
				setTimeout('dbg_scroll_to_bottom();',50);

				// trick to get around NS & MOZ render bug
				__DBG_WINDOW.__dDebugDiv.style.display = 'none';
				__DBG_WINDOW.__dCmdTextInput.style.display = 'none';
				setTimeout("__DBG_WINDOW.__dDebugDiv.style.display = '';", 1);
				setTimeout("__DBG_WINDOW.__dCmdTextInput.style.display = '';", 1);
			}
		}
	}
}
//============================================================================================
function dbg_display_help_dialog(){
	var op = '';
	op += '<div style="padding:6px; text-align:center; color:#ffff66; border-bottom: 1px solid #ffff66;">JavaScript Debugger v'+__DBG_VERSION+'</div>';
	echo_html(op);
}
//============================================================================================
//  DEBUGGER AND APP EVENT HANDLERS  |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//============================================================================================
function dbg_app_onkeypress(e){
	var jEvent = e ? e : event;
	var nCharCode = jEvent.which ? jEvent.which : jEvent.keyCode;
	// detect CTRL-SHIFT-D as a hotkey to open the debugger
	if(jEvent.ctrlKey && jEvent.shiftKey && (nCharCode == 4 || nCharCode == 68)){
		//alert_r(__DBG_WINDOW);
		// only open a debug window if it's not already opened
		if(__DBG_WINDOW == null){ 
			req_debug_window(); 
			__DBG_STATE.win_open = 1;
		}
		// different browsers treat references to a window differently after they've been closed. Some browsers will
		// maintain the window object and let you check the 'closed' property, however browsers like Opera will make
		// the window object a totally empty object with no attributes. So, if the 'closed' property doesn't exist,
		// or, if it does, it must be set to true before we open the debugger window
		else if(typeof(__DBG_WINDOW) == 'object' && (__DBG_WINDOW.closed === void(0) || __DBG_WINDOW.closed === true)){ 
			req_debug_window(); 
			__DBG_STATE.win_open = 1;
		}
	}
}
//============================================================================================
function dbg_app_onunload(){
	// if the debugger is open and the app closes
	if(__DBG_WINDOW != null){
		if(!__DBG_WINDOW.closed){
			dbg_capture_dbg_state(); // capture the debugger window's state
			__DBG_STATE.win_open = 1;
			__DBG_WINDOW.close(); // close the debugger too
		}
	}

	// when the app closes, save the debugger's last known geometry into a cookie
	dbg_store_dbg_state();
}
//============================================================================================
function dbg_win_onunload(){
	// since the debugger window is being closed, update the __DBG_STATE
	// object with it's last known geometry and state
	dbg_capture_dbg_state();
	__DBG_STATE.win_open = 0;
}
//============================================================================================
function dbg_win_onresize(){
	// Detect the dimensions of the browsers client area
	var nNewHeight = __DBG_WINDOW.innerHeight ? __DBG_WINDOW.innerHeight : __DBG_WINDOW.document.body.clientHeight;

	// apply geometry to our debugger objects
	__DBG_WINDOW.__dCmdTextInput.style.top = nNewHeight - 16 + 'px';
	__DBG_WINDOW.__dDebugDiv.style.height = nNewHeight - 17 + 'px';

	// trick to get around NS & MOZ render bug
	__DBG_WINDOW.__dDebugDiv.style.display = 'none';
	__DBG_WINDOW.__dCmdTextInput.style.display = 'none';
	setTimeout("__DBG_WINDOW.__dDebugDiv.style.display = '';", 1);
	setTimeout("__DBG_WINDOW.__dCmdTextInput.style.display = '';", 1);
}
//============================================================================================
function dbg_win_onerror(e){
	var jEvent = e ? e : event;
	var jObj = new Object();
	var aPropVals = '';
	var cOutput = '';

	// it appears that the new standard says that error handlers will
	// have three strings passed in (message, fileNumber, and lineNumber) 
	if(arguments.length > 1){
		jObj.message = arguments[0];
		jObj.fileName = arguments[1];
		jObj.lineNumber = arguments[2];
	}
	// otherwise, for older browsers (NS6), we can only look at the verbose
	// event object
	else{ jObj = jEvent; }

	aPropVals = dbg_get_prop_vals(jObj);
	cOutput = dbg_format_prop_vals(aPropVals);
	cOutput = "<span style='color:#ff0000;'>" +
		  "### JAVASCRIPT ERROR ##################################<br>" + 
		  html_to_text(cOutput) +
		  "#######################################################" +
		  "</span>";
	echo_html(cOutput);

	return true; // to suppress standard JavaScript Error dialog
}
//============================================================================================
function dbg_doc_onkeyup(_jEvent){
	var nCharCode = _jEvent.which ? _jEvent.which : _jEvent.keyCode;
	switch(nCharCode){
		case 38: // Up Arrow
			if((__DBG_CMD_HIST_PTR - 1) >= 0){
				__DBG_CMD_HIST_PTR--;
				__DBG_WINDOW.__dCmdTextInput.value = __DBG_CMD_HIST[__DBG_CMD_HIST_PTR];
			}
			setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 1);
		break;
		case 40: // Down Arrow
			if((__DBG_CMD_HIST_PTR + 1) < __DBG_CMD_HIST.length){
				__DBG_CMD_HIST_PTR++;
				__DBG_WINDOW.__dCmdTextInput.value = __DBG_CMD_HIST[__DBG_CMD_HIST_PTR];
			}
			else{
				__DBG_CMD_HIST_PTR =  __DBG_CMD_HIST.length;
				__DBG_WINDOW.__dCmdTextInput.value = '';
			}
			setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 1);
		break;
		case 13: // Enter
			if(__DBG_WINDOW.__dCmdTextInput.value != ''){
				// store the command string in the history regardless of whether it succeeds
				__DBG_CMD_HIST.push(__DBG_WINDOW.__dCmdTextInput.value);
				__DBG_CMD_HIST_PTR =  __DBG_CMD_HIST.length;

				// look for the EXIT keyword
				if(__rxEXIT.test(__DBG_WINDOW.__dCmdTextInput.value)){
					__DBG_WINDOW.close();
				}
				// look for the CLEAR keyword
				else if(__rxCLEAR.test(__DBG_WINDOW.__dCmdTextInput.value)){
					__DBG_WINDOW.__dDebugDiv.innerHTML = __cDEBUG_OUTPUT = "";
					setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 1);
				}
				// look for the HISTORY keyword
				else if(__rxHISTORY.test(__DBG_WINDOW.__dCmdTextInput.value)){
					echo("\n" + __DBG_CMD_HIST.join("\n") + "\n");
					setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 1);
				}
				// otherwise, attempt to eval
				else{
					// hopefully we're executing valid script	
					try{
						eval(__DBG_WINDOW.__dCmdTextInput.value);
					}
					// if not, capture the error here
					catch(jError){
						// send the error to our error handler
						dbg_win_onerror(jError);
					}
					setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 1);
				}

				// clear the last command string
				__DBG_WINDOW.__dCmdTextInput.value = '';
			}
		break;
		case 9: // TAB 
				dbg_tab_complete();
				setTimeout("__DBG_WINDOW.__dCmdTextInput.focus();", 1);
		break;
		default:
		break;
	}
}
//============================================================================================
//	UTILITY FUNCTIONS TAYLORED FOR DEVELOPER USE  ||||||||||||||||||||||||||||||||||||||||||||
//============================================================================================
function html_to_text(_cStr){
	_cStr = _cStr ? _cStr : '';
	if(arguments.length == 1){
		 _cStr = _cStr.replace(/ /g, "&#160;"); // force browser to not eat multiple spaces
		 _cStr = _cStr.replace(/</g, "&lt;"); // render open tags correctly
		 _cStr = _cStr.replace(/>/g, "&gt;"); // render close tags correctly
		 _cStr = _cStr.replace(/\n/g, "<br>"); // render newlines as break tags 
		 _cStr = _cStr.replace(/\r/g, "<br>"); // render newlines as break tags 
	}
	return _cStr;
}
//============================================================================================
function echo(_cDebugMsg){
	//format the debugger output
	_cDebugMsg = html_to_text(_cDebugMsg + "\n");

	// render output to debugger window
	dbg_append_output(_cDebugMsg);
}
//============================================================================================
function echo_html(_cDebugMsg){
	// render output to debugger window
	dbg_append_output(_cDebugMsg + "<br>");
}
//============================================================================================
function echo_props(_jObj){
	var aPropVals = dbg_get_prop_vals(_jObj);
	var cOutput = dbg_format_prop_vals(aPropVals);
	if(cOutput != ""){ echo(cOutput); }
}
//============================================================================================
// Create a few short hand aliases to our echo functions (I'm so lazy)
function p(_str){ echo(_str); }
function ph(_str){ echo_html(_str); }
function pp(_obj){ echo_props(_obj); }
//============================================================================================
// used for debugging of the debugger... In cases where I haven't been able to get the debugger working
// I use this function to inspect objects properties, albeit in a much less attractive state
function alert_r(_jObj){
	var str = '';
	var cAttrib, mVal;
	for(cAttrib in _jObj){
		// first, see if the client script is allowed to access the variable
		try{ mVal = _jObj[cAttrib]; }
		catch(jError){ mVal = '### BROWSER PROTECTED VALUE ###'; }

		// next, see if the value can be converted to a string (some of Opera's objects can't be (like the sun,java,Packages,etc.)
		try{ mVal += ' '; } // implicite string conversion
		catch(e){ mVal = "### UNKNOWN VALUE (STRING CONVERSION FAILED) ###"; }

		str += cAttrib+" = "+mVal+"\n";
	}
	alert(str);
}
//============================================================================================
