(function ()
{
	window.JCCodeEditor = function(arConfig, MESS)
	{
		var _this = this;
		this.actionCount = 0;
		this.arConfig = arConfig;
		this.MESS = MESS;
		this.arSyntaxes = {};

		this.pTA = BX(this.arConfig.textareaId);
		if (!this.pTA || !BX.isNodeInDom(this.pTA))
			return false;

		this.saveSettings = !!arConfig.saveSettings;

		this.highlightMode = !!arConfig.highlightMode && this.IsValidBrowser();

		this.theme = arConfig.theme;

		this.tabSize = 4;
		this.undoDepth = 30;
		this.timeout = 100;
		this.delay = 200;
		this.pollInterval = 100;
		this.cursorInterval = 500;
		this.indentUnit = 4;
		this._cachedWidthFor = 0;
		this._cachedWidth = 0;
		this.prevInput = "";
		this._cachedHeight = null;
		this._cachedHeightFor = null;
		this._measurePre = null;
		this.lineNums = 0;
		this.tabSymTA = "        "; // 8 spaces;
		this.arBrackets = {
			"(": ")>",
			")": "(<",
			"[": "]>",
			"]": "[<",
			"{": "}>",
			"}": "{<"
		};

		if (this.arConfig.forceSyntax && {'php': 1, 'js': 1, 'css': 1, 'sql': 1}[this.arConfig.forceSyntax])
			this.syntaxName = this.arConfig.forceSyntax;
		else
			this.syntaxName = 'php'; // php | js | css | sql

		this.InitKeyEngine();
		this.BuildSceleton();

		if (BX.browser.IsIOS())
			this.pInput.style.width = "0px";

		if (!BX.browser.IsSafari() && !BX.browser.IsChrome())
			this.pScroller.draggable = true;

		this.pLinesCont.style.outline = "none";
		this.FocusInput();

		if (BX.browser.IsMac())
		{
			this.pScrollbar.style.zIndex = -2;
			this.pScrollbar.style.visibility = "hidden";
		}
		else if (BX.browser.IsIE() && !BX.browser.IsIE9())
		{
			this.pScrollbar.style.minWidth = "18px";
		}

		try
		{
			this.GetCharWidth();
		}
		catch (e)
		{
			return;
		}

		// Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
		this.pollDelayed = new Delayed();
		this.highlight = new Delayed();

		this.oDoc = new JCDocHolder([new JCLineHolder([new JCLine("")])]);
		this.focused = false;
		this.LoadSyntax();

		this.oSel = {
			from: {line: 0, ch: 0},
			to: {line: 0, ch: 0},
			inverted: false
		};

		this.lastClick = false;
		this.lastDoubleClick = false;
		this._lastScrollTop = 0;
		this._lastStoppedKey = null;
		this.suppressEdits = false;

		this.callbacks = null;
		this.arChanges = [];

		this.displayOffset = 0;
		this.showingFrom = 0;
		this.showingTo = 0;
		this.lastSizeC = 0;
		this.bracketHighlighted = null;
		this.maxLine = this.GetLine(0);
		this.updateMaxLine = false;
		this.maxLineChanged = true;
		this._tabCache = {};
		this.pollingFast = false;
		this.goalColumn = null;

		// Initialize the content.
		this.Action(this.SetValue, this)(this.pTA.value || "");
		this.updateInput = false;

		this.oHistory = new History();

		// Register our event handlers.
		BX.bind(this.pScroller, "mousedown", this.Action(this.OnMouseDown, this));
		BX.bind(this.pScroller, "dblclick", this.Action(this.OnDoubleClick, this));
		//BX.bind(this.pLinesCont, "selectstart", preventDefault);
		BX.bind(this.pLinesCont, "selectstart", BX.PreventDefault);
		BX.bind(this.pScroller, "scroll", BX.proxy(this.OnScrollMain, this));
		BX.bind(this.pScrollbar, "scroll", BX.proxy(this.OnScrollBar, this));
		BX.bind(this.pScrollbar, "mousedown", function ()
		{
			if (_this.highlightMode && _this.focused)
				setTimeout(_this.FocusInput, 0);
		});
		//BX.bind(window, "resize", BX.proxy()

		BX.bind(this.pInput, "keyup", this.Action(this.OnKeyUp, this));
		BX.bind(this.pInput, "input", BX.proxy(this.FastPoll, this));
		BX.bind(this.pInput, "keydown", this.Action(this.OnKeyDown, this));
		BX.bind(this.pInput, "keypress", this.Action(this.OnKeyPress, this));
		BX.bind(this.pInput, "focus", BX.proxy(this.OnFocus, this));
		BX.bind(this.pInput, "blur", BX.proxy(this.OnBlur, this));

		BX.bind(this.pScroller, "paste", function ()
		{
			_this.FocusInput();
			_this.FastPoll();
		});
		BX.bind(this.pInput, "paste", BX.proxy(this.FastPoll, this));
		BX.bind(this.pInput, "cut", this.Action(function(){_this.ReplaceSelection("");}, this));

		setTimeout(BX.proxy(this.OnFocus,this), 20);

		if (this.theme != 'dark')
		{
			this.theme = 'dark';
			this.SwitchTheme();
		}

		if (!this.highlightMode)
		{
			this.highlightMode = true;
			this.SwitchHightlightMode();
		}

		// Autosave handlers
		var pForm = this.pTA.form;
		if (pForm && pForm.BXAUTOSAVE)
		{
			try{
				BX.addCustomEvent(this, 'OnAfterActionSelectionChanged', function(){
					pForm.BXAUTOSAVE.Init();
				});

				BX.addCustomEvent(pForm, 'onAutoSave', function (ob, data)
				{
					if (_this.highlightMode)
						_this.Save();
					data[_this.pTA.name] = _this.GetTAValue();
				});

				BX.addCustomEvent(pForm, 'onAutoSaveRestore', function (ob, data)
				{
					if (_this.highlightMode)
					{
						_this.Action(_this.SetValue, _this)(data[_this.pTA.name]);
					}
					else
					{
						_this.pTA.value = data[_this.pTA.name];
						_this.CheckLineSelection();
					}
				});
			}catch(e){}
		}
	};

	window.JCCodeEditor.prototype = {
		BuildSceleton: function()
		{
			var _this = this;
			this.pDiv = this.pTA.parentNode.appendChild(BX.create("DIV", {props:{className: 'bxce bxce-hls'}}));
			this.pWarning = this.pDiv.appendChild(BX.create("DIV", {props:{className: 'bxce-warning-cont'}, text: this.MESS.HighlightWrongwarning, style:{display: 'none'}}));
			this.pInnerContHL = this.pDiv.appendChild(BX.create("DIV", {props:{className: "bxce-inner-hl"}}));

			// For textarea MODE
			this.pBaseContTA = this.pDiv.appendChild(BX.create("DIV", {props:{className: 'bxce-base-cont'}, style: {display: 'none'}}));
			this.pBaseContTA.onclick = function(e){BX.focus(_this.pTA);};


			// Relative div - contains textarea, line numbers, - ONLY for switch off hightlighting mode
			this.pContTA = this.pBaseContTA.appendChild(BX.create("DIV", {props: {className: 'bxce-cont'}}));
			this.pLineNumTABgTA = this.pBaseContTA.appendChild(BX.create("DIV", {props: {className: 'bxce-line-num-bg'}}));
			// Line numbers
			this.pLineNumTA = this.pContTA.appendChild(BX.create("DIV", {props: {className: 'bxce-line-num bxce-font'}}));
			this.pContTA.appendChild(this.pTA);
			this.pTA.className = 'bxce-ta';
			this.pTA.style.display = "none";
			this.pTA.removeAttribute("cols");
			this.pTA.removeAttribute("rows");
			this.pTA.setAttribute("spellcheck", false);
			this.pTA.setAttribute("hidefocus", true);
			this.pTA.setAttribute("autocomplete", "off");
			this.pTA.setAttribute("autocorrect", "off");
			this.pTA.setAttribute("autocapitalize", "off");
			this.pTA.setAttribute('wrap', "off");
			this.pTA.onkeyup = BX.proxy(this.TextOnKeyup, this);
			this.pTA.onkeydown = BX.proxy(this.TextOnKeydown, this);
			this.pTA.onmousedown = BX.proxy(this.TextOnMousedown, this);

			this.pInputCont = this.pInnerContHL.appendChild(BX.create("DIV", {props:{className: "bxce-inp-cont"}}));
			this.pInput = this.pInputCont.appendChild(BX.create("TEXTAREA", {props:{className: "bxce-inp"}}));
			this.pInput.setAttribute("wrap", "off");
			this.pInput.setAttribute("autocorrect", "off");
			this.pInput.setAttribute("autocapitalize", "off");

			this.pScrollbar = this.pInnerContHL.appendChild(BX.create("DIV", {props:{className: "bxce-scrollbar"}}));
			this.pScrollbarInner = this.pScrollbar.appendChild(BX.create("DIV", {props:{className: "bxce-scrollbar-inner"}})); // scrollbarInner

			this.pScroller = this.pInnerContHL.appendChild(BX.create("DIV", {props:{className: "bxce-scroller", tabIndex: -1}}));
			this.pSizeCont = this.pScroller.appendChild(BX.create("DIV", {props:{className: "bxce-size-cont", tabIndex: -1}}));
			this.pMover = this.pSizeCont.appendChild(BX.create("DIV", {props:{className: "bxce-inner-size-cont"}}));
			this.pLineNum = this.pMover.appendChild(BX.create("DIV", {props:{className: "bxce-hl-line-num"}}));


			this.pLineNumText = this.pLineNum.appendChild(BX.create("DIV", {props:{className: "bxce-hl-line-num-text"}}));
			this.pHighlight = this.pMover.appendChild(BX.create("DIV", {props:{className: "bxce-highlight"}}));
			this.pLinesCont = this.pHighlight.appendChild(BX.create("DIV", {props:{className: "bxce-lines-cnt"}}));

			this.pMeasure = this.pLinesCont.appendChild(BX.create("DIV", {props:{className: "bxce-liner-cont"}}));
			this.pCursor = this.pLinesCont.appendChild(BX.create("PRE", {props:{className: "bxce-cursor"}, text: "\u00a0"}));
			this.pWidthForcer = this.pLinesCont.appendChild(BX.create("PRE", {props:{className: "bxce-cursor"}, style: {visibility: 'hidden'}, text: "\u00a0"}));
			this.pSelectionDiv = this.pLinesCont.appendChild(BX.create("DIV", {props:{className: "bxce-sel-cont"}}));
			this.pLineDiv = this.pLinesCont.appendChild(BX.create("DIV"));

			// Foot cont
			this.pFootCont = this.pDiv.appendChild(BX.create("DIV", {props:{className: 'bxce-foot-cont'}}));
			this.pFastLineInp = this.pFootCont.appendChild(BX.create("SPAN", {props:{className: 'bxce-go-to-line-cont'}})).appendChild(BX.create("INPUT", {props:{className: 'bxce-go-to-line', title: this.MESS.GoToLine}}));

			this.pFastLineInp.onfocus = function(){setTimeout(function(){_this.pFastLineInp.select();}, 10);};
			this.pFootCont.onclick = function(){setTimeout(function(){_this.pFastLineInp.select();}, 10);};
			this.pFastLineInp.onkeydown = BX.proxy(this.FastGoToLine, this);

			this.pCurPosCont = this.pFootCont.appendChild(BX.create("SPAN", {props:{className: 'bxce-info-cur-cont'}, html: this.MESS.Line + ":"}));
			this.pInfoCurLine = this.pCurPosCont.appendChild(BX.create("SPAN", {props:{className: 'bxce-info-cur-line', title: this.MESS.LineTitle}}));
			this.pCurPosCont.appendChild(document.createTextNode(this.MESS.Char + ":"));
			this.pInfoCurChar = this.pCurPosCont.appendChild(BX.create("SPAN", {props:{className: 'bxce-info-cur-char', title: this.MESS.CharTitle}}));

			this.pTotalCont = this.pFootCont.appendChild(BX.create("SPAN", {props:{className: 'bxce-info-total-cont'}, html: this.MESS.Total + "  " + this.MESS.Lines + ":"}));
			this.pInfoTotLines = this.pTotalCont.appendChild(BX.create("SPAN", {props:{className: 'bxce-info-total-line'}}));

			// Mode toggle
			this.pModeToggle = this.pFootCont.appendChild(BX.create("A", {props:{href: 'javascript:void(0)',className: 'bxce-mode-link' + (this.highlightMode ? ' bxce-mode-link-on' : ''), title: this.MESS.EnableHighlightTitle}, html: '<span class="bxce-mode-txt">' + this.MESS.EnableHighlight + '</span><i></i>'}));
			this.pModeToggle.onclick = BX.proxy(this.SwitchHightlightMode, this);

			// Theme toggle
			this.pThemeToggle = this.pFootCont.appendChild(BX.create("A", {props:{href: 'javascript:void(0)',className: 'bxce-theme-toggle'}, html: '<i></i><span class="bxce-theme-toggle-text">' + this.MESS.LightTheme + '</span>'}));
			this.pThemeToggle.onclick = BX.proxy(this.SwitchTheme, this);
			this.pThemeToggleText = this.pThemeToggle.childNodes[1];//

			var w, h;
			if (this.arConfig.width)
				w = parseInt(this.arConfig.width);
			if (this.arConfig.height)
				h = parseInt(this.arConfig.height);

			if (this.pDiv.parentNode && !w)
				w = parseInt(this.pDiv.parentNode.offsetWidth) - (BX.browser.IsIE() ? 10 : 2);
			if (this.pDiv.parentNode && !h)
				h = parseInt(this.pDiv.parentNode.offsetHeight) - 2;

			if (!w || isNaN(w) || h < 100)
				w = 900;
			if (!h || isNaN(h) || h < 100)
				h = 400;

			this.AdjustSceletonSize(w, h);
		},

		Action: function(func, obj)
		{
			var _this = this;
			if (obj == undefined)
				obj = this;

			return function ()
			{
				if (!_this.actionCount++)
					_this.OnBeforeAction();

				try
				{
					var res = func.apply(obj, arguments);
				}
				finally
				{
					if (!--_this.actionCount)
						_this.OnAfterAction();
				}

				return res;
			};
		},

		OnBeforeAction: function()
		{
			BX.onCustomEvent(this, "OnBeforeAction");
			this.updateInput = null;
			this.userSelChange = null;
			this.textChanged = null;
			this.changes = [];
			this.arChanges = [];
			this.selectionChanged = false;
			this.callbacks = [];
		},

		OnAfterAction: function()
		{
			if (this.updateMaxLine)
				this.ComputeMaxLength();

			if (this.maxLineChanged)
			{
				var
					cursorWidth = this.pWidthForcer.offsetWidth,
					left = this.MeasureLine(this.maxLine, this.maxLine.text.length).left;

				if (!BX.browser.IsIE() || BX.browser.IsIE9())
				{
					this.pWidthForcer.style.left = left + "px";
					this.pLinesCont.style.minWidth = (left + cursorWidth) + "px";
				}
				this.maxLineChanged = false;
			}

			var newScrollPos, updated;
			if (this.selectionChanged)
			{
				var coords = this.CalculateCursorCoords();
				newScrollPos = this.CalculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
			}

			if (this.arChanges.length || newScrollPos && newScrollPos.scrollTop != null)
				updated = this.UpdateDisplay(this.arChanges, true, newScrollPos && newScrollPos.scrollTop);

			if (!updated)
			{
				if (this.selectionChanged)
					this.UpdateSelection();
				if (this.lineNumDirty)
					this.UpdateLineNum();
			}

			if (newScrollPos)
				this.ScrollCursorIntoView();
			if (this.selectionChanged)
				this.RestartBlink();

			if (this.focused && (this.updateInput === true || (this.updateInput !== false && this.selectionChanged)))
				this.ResetInput(this.userSelChange);

			if (this.selectionChanged)
			{
				BX.onCustomEvent(this, "OnAfterActionSelectionChanged");
				setTimeout(this.Action(this.CheckMatchingBrackets, this), 20);
			}

			this.SetStatusBarInfo(this.oSel.to);

			BX.onCustomEvent(this, "OnAfterAction");
		},

		CheckMatchingBrackets: function()
		{
			if (this.bracketHighlighted)
			{
				this.bracketHighlighted();
				this.bracketHighlighted = null;
			}

			if (this.PosEq(this.oSel.from, this.oSel.to))
				this.MatchBrackets();
		},

		MatchBrackets: function()
		{
			var
				style,
				head = this.oSel.inverted ? this.oSel.from : this.oSel.to,
				line = this.GetLine(head.line),
				pos = head.ch - 1;

			var match = (pos >= 0 && this.arBrackets[line.text.charAt(pos)]) || this.arBrackets[line.text.charAt(++pos)];
			if (!match)
				return;

			var
				_this = this,
				off, i,
				ch = match.charAt(0),
				forward = match.charAt(1) == ">",
				d = forward ? 1 : -1,
				st = line.styles;

			for (off = pos + 1, i = 0; i < st.length; i += 2)
			{
				if ((off -= st[i].length) <= 0)
				{
					style = st[i + 1];
					break;
				}
			}

			var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;

			function scan(line, from, to)
			{
				if (!line.text)
					return;
				var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
				for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2 * d)
				{
					var text = st[i];
					if (st[i + 1] != style)
					{
						pos += d * text.length;
						continue;
					}
					for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos += d)
					{
						if (pos >= from && pos < to && re.test(cur = text.charAt(j)))
						{
							var match = _this.arBrackets[cur];
							if (match.charAt(1) == ">" == forward)
								stack.push(cur);
							else if (stack.pop() != match.charAt(0))
								return {pos: pos, match: false};
							else if (!stack.length)
								return {pos: pos, match: true};
						}
					}
				}
			}

			var e, first, found;
			for (i = head.line, e = forward ? Math.min(i + 100, this.oDoc.size) : Math.max(-1, i - 100); i != e; i += d)
			{
				line = this.GetLine(i);
				first = i == head.line;
				found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
				if (found)
					break;
			}

			if (!found)
				found = {pos: null, match: false};


			var
				cn = found.match ? "bxce-hl-bracket" : "bxce-non-hl-bracket",
				firstBracket = this.HightlightFrag({line: head.line, ch: pos}, {line: head.line, ch: pos + 1}, cn),
				secondBracket = found.pos != null && this.HightlightFrag({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, cn);

			this.bracketHighlighted = this.Action(function ()
			{
				firstBracket.Clear();
				if (secondBracket)
					secondBracket.Clear();
			}, this);
		},

		SetStatusBarInfo: function(sel)
		{
			if (this.highlightMode || sel.nbLine == undefined)
			{
				this.pInfoCurLine.innerHTML = sel.line + 1;
				this.pInfoCurChar.innerHTML = sel.ch;
				this.pInfoTotLines.innerHTML = this.oDoc.size;
				this.pFastLineInp.value = sel.line + 1;
			}
			else
			{
				this.pInfoCurLine.innerHTML = sel.linePos;
				this.pInfoCurChar.innerHTML = sel.carretPos;
				this.pInfoTotLines.innerHTML = sel.nbLine;
				this.pFastLineInp.value = sel.linePos;
			}
		},

		FastGoToLine: function(e)
		{
			if(!e)
				e = window.event;

			var key = e.which || e.keyCode;
			if (key == 13)
			{
				this.GoToLine(this.pFastLineInp.value);
				return BX.PreventDefault(e);
			}
		},

		GoToLine: function(line)
		{
			line = parseInt(line, 10);
			if (isNaN(line))
				return;

			if (this.highlightMode)
			{
				line--;
				if (line < 0)
					line = 0;
				if (line >= this.oDoc.size)
					line = this.oDoc.size - 1;

				this.OnBeforeAction();
				this.SetCursor(line, 0, true);
				this.OnAfterAction();
				setTimeout(BX.proxy(this.FocusInput, this), 20);
			}
			else
			{
				if (line < 0)
					line = 0;

				var
					start = 0, i, dforIe = (BX.browser.IsIE() ? 0 : 1),
					value = this.GetTAValue(),
					lines = value.split("\n"),
					linesCount = lines.length;

				if(line > linesCount)
				{
					start = value.length;
				}
				else
				{
					for(i = 0; i < line - 1; i++)
						start += lines[i].length + dforIe;
				}

				this.SelectTA(start, 0);
				this.MoveLineSelection();

				if (BX.browser.IsChrome() || BX.browser.IsSafari())
				{
					if(this.pLineNumTA.childNodes[line - 1])
						this.pLineNumTA.childNodes[line - 1].scrollIntoView(false);
				}

				return start;
			}
		},

		SelectTA: function(start, len)
		{
			BX.focus(this.pTA);
			var l = this.pTA.value.length;
			if (start < 0)
				start = 0;
			if (start > l)
				start = l;
			var end = start + len;
			if (end > l)
				end = l;

			if(BX.browser.IsIE())
				this.SetIETASelection(start, end);
			else
				this.pTA.setSelectionRange(start, end);
		},

		SetIETASelection: function(start, end)
		{
			var
				val = this.GetTAValue().replace(/\r/g, ""),
				nbLineStart = val.substr(0, start).split("\n").length - 1,
				nbLineEnd = val.substr(0, end).split("\n").length - 1,
				range = document.selection.createRange();

			start += nbLineStart;
			end += nbLineEnd;

			range.moveToElementText(this.pTA);
			range.setEndPoint('EndToStart', range);

			range.moveStart('character', start - nbLineStart);
			range.moveEnd('character', end - nbLineEnd - (start - nbLineStart));
			range.select();
		},

		MoveLineSelection: function(bTimeout)
		{
			if (this.checkLineTimeout)
				this.checkLineTimeout = clearTimeout(this.checkLineTimeout);

			if (bTimeout === true)
				this.checkLineTimeout = setTimeout(BX.proxy(this.MoveLineSelection, this), 5);
			else
				this.CheckLineSelection(false);
		},

		SwitchTheme: function()
		{
			if (this.theme == 'dark')
			{
				this.theme = 'light';
				BX.addClass(this.pDiv, 'bxce--light');
				this.pThemeToggleText.innerHTML = this.MESS.DarkTheme;
			}
			else
			{
				BX.removeClass(this.pDiv, 'bxce--light');
				this.theme = 'dark';
				this.pThemeToggleText.innerHTML = this.MESS.LightTheme;
			}

			if (this.highlightMode)
			{
				if (!this.focused)
				{
					this.FocusInput();
					this.OnFocus();
				}
			}
			else
			{
				BX.focus(this.pTA);
			}

			this.SaveUserOption('theme', this.theme);
		},

		SwitchHightlightMode: function()
		{
			this.highlightMode = !this.highlightMode;

			if (this.__warnTimeout)
				this.__warnTimeout = clearTimeout(this.__warnTimeout);

			if(this.highlightMode && !this.IsValidBrowser())
			{
				var _this = this;
				this.pWarning.style.display = "";
				this.__warnTimeout = setTimeout(function(){_this.pWarning.style.display = "none";}, 4000);
			}
			else
			{
				this.pWarning.style.display = "none";
			}

			if (this.highlightMode)
			{
				this.pInnerContHL.style.display = "";
				this.pBaseContTA.style.display = "none";
				this.pTA.style.display = "none";
				BX.addClass(this.pModeToggle, 'bxce-mode-link-on');
				BX.addClass(this.pDiv, 'bxce-hls');

				if (!BX.browser.IsSafari() && !BX.browser.IsChrome())
					this.pScroller.setAttribute('draggable', true);

				this.Action(this.SetValue, this)(this.pTA.value || "");
				if (!this.focused)
				{
					this.FocusInput();
					this.OnFocus();
				}
			}
			else
			{
				this.Save();

				var
					w = this.pHighlight.offsetWidth - 60,
					h = this.pHighlight.offsetHeight;

				this.pTA.style.width = w + "px";
				this.pContTA.style.width = w + "px";

				this.pTA.style.height = h + "px";

				this.pInnerContHL.style.display = "none";
				this.pTA.style.display = "";
				this.pBaseContTA.style.display = "";
				BX.focus(this.pTA);
				setTimeout(BX.proxy(this.ManageSize, this), 100);

				BX.removeClass(this.pModeToggle, 'bxce-mode-link-on');
				BX.removeClass(this.pDiv, 'bxce-hls');

				if (!BX.browser.IsSafari() && !BX.browser.IsChrome())
					this.pScroller.removeAttribute('draggable');
			}

			this.SaveUserOption('highlight', this.highlightMode ? 1 : 0);
		},

		IsValidBrowser: function()
		{
			return !BX.browser.IsIE() || BX.browser.IsDoctype();
		},

		SaveUserOption: function(option, value)
		{
			if (this.saveSettings)
				BX.userOptions.save('fileman', 'code_editor', option, value);
		},

		SetValue: function(code)
		{
			var top = {line: 0, ch: 0};
			this.UpdateLines(
				top,
				{
					line: this.oDoc.size - 1,
					ch: this.GetLine(this.oDoc.size - 1).text.length
				},
				this.ExplodeLines(code), top, top
			);
			this.updateInput = true;
		},

		GetValue: function(lineSep)
		{
			var text = [];
			this.oDoc.Iteration(0, this.oDoc.size, function(line){text.push(line.text);});
			return text.join(lineSep || "\n");
		},

		GetTAValue: function()
		{
			return this.pTA.value;
		},

		SetTAValue: function(value)
		{
			if (value == undefined)
				value = this.GetTAValue();
			else
				value = value.replace(/\r/g, "");

			this.pTA.value = value;
		},

		Save: function()
		{
			this.pTA.value = this.GetValue();
		},

		AdjustSceletonSize: function(w, h)
		{
			w = parseInt(w);
			h = parseInt(h);
			var
				baseW = w,
				botH = 20,
				baseH = h - botH;

			this.pScroller.style.height = baseH + 'px';
			this.pLineNumTABgTA.style.height = baseH + 'px';
			this.pBaseContTA.style.height = baseH + 'px';
			this.pBaseContTA.style.width = baseW + 'px';

			this.pDiv.style.width = w + 'px';
			this.pDiv.style.height = h + 'px';
		},

		Resize: function(w, h)
		{
			w = parseInt(w);
			h = parseInt(h);
			this.AdjustSceletonSize(w, h);
			this.UpdateDisplay(true);
		},

		HasSelection: (window.getSelection ?
			function (inp)
			{
				var res = false;
				try
				{
					res = inp.selectionStart != inp.selectionEnd;
				}
				catch (e){}
				return res;
			}
			:
			function (inp)
			{
				var res = false;
				try
				{
					res = inp.ownerDocument.selection.createRange();
				}
				catch (e){}

				if (!res || res.parentElement() != inp)
					res = false;
				else if(res)
					res = res.compareEndPoints("StartToEnd", res) != 0;
				return res;
			}
			),

		ExplodeLines: ("\n\nb".split(/\n/).length == 3 ?
			function (s)
			{
				return s.split(/\r\n?|\n/);
			}
			:
			function (s) // Bug in IE with split function
			{
				var
					nl, ln, rt,
					pos = 0,
					res = [],
					l = s.length;

				while (pos <= l)
				{
					nl = s.indexOf("\n", pos);
					if (nl == -1)
						nl = s.length;
					ln = s.slice(pos, s.charAt(nl - 1) == "\r" ? nl - 1 : nl);
					rt = ln.indexOf("\r");
					if (rt != -1)
					{
						res.push(ln.slice(0, rt));
						pos += rt + 1;
					}
					else
					{
						res.push(ln);
						pos = nl + 1;
					}
				}
				return res;
			}
			),

		HightlightFrag: function(from, to, className)
		{
			from = this.ClipPos(from);
			to = this.ClipPos(to);
			var _this = this;

			var oHL = this.GetHighlighter();
			if (!this.PosLess(from, to))
				return oHL;

			function add(line, from, to, className)
			{
				_this.GetLine(line).addMark(new JCHighlightedText(from, to, className, oHL));
			}

			if (from.line == to.line)
			{
				add(from.line, from.ch, to.ch, className);
			}
			else
			{
				add(from.line, from.ch, null, className);
				for (var i = from.line + 1, e = to.line; i < e; ++i)
					add(i, null, null, className);
				add(to.line, null, to.ch, className);
			}
			this.arChanges.push({from: from.line, to: to.line + 1});
			return oHL;
		},

		GetHighlighter: function()
		{
			var oHL = {'set': []};
			oHL.Clear = this.Action(function(){
				var
					i, line, j, lineN,
					min = Infinity,
					max = -Infinity;

				for (i = 0; i < oHL.set.length; ++i)
				{
					line = oHL.set[i];
					if (!line.marked || !line.parent)
						continue;

					lineN = lineNo(line);
					min = Math.min(min, lineN);
					max = Math.max(max, lineN);

					line.marked = [];
				}

				if (min != Infinity)
					this.arChanges.push({from: min, to: max + 1});
			}, this);

			return oHL;
		},

		GetCursor: function (start)
		{
			if (start == undefined)
				start = this.oSel.inverted;
			return this.CopyPos(start ? this.oSel.from : this.oSel.to);
		},

		SetCursor: function(line, ch, user)
		{
			var pos = this.ClipPos({line: line, ch: ch || 0});
			if (user)
				this.SetSelectionUser(pos, pos);
			else
				this.SetSelection(pos, pos);
		},

		ClipLine: function(n)
		{
			return Math.max(0, Math.min(n, this.oDoc.size - 1));
		},

		ClipPos: function(pos)
		{
			var res = pos;

			if (pos.line < 0)
			{
				res = {
					line: 0,
					ch: 0
				};
			}
			else if (pos.line >= this.oDoc.size)
			{

				res = {
					line: this.oDoc.size - 1,
					ch: this.GetLine(_this.oDoc.size - 1).text.length
				};
			}
			else
			{
				var
					ch = pos.ch,
					linelen = this.GetLine(pos.line).text.length;

				if (ch == null || ch > linelen)
					res = {line: pos.line, ch: linelen};
				else if (ch < 0)
					res = {line: pos.line, ch: 0};
			}

			return res;
		},

		GetLine: function(n)
		{
			return this.GetLineAt(this.oDoc, n);
		},

		GetLineAt: function(chunk, n)
		{
			while (!chunk.lines)
			{
				var i, child, size;
				for (i = 0; ; ++i)
				{
					child = chunk.children[i];
					if (!child || !child.GetSize)
						break;

					size = child.GetSize();
					if (n < size)
					{
						chunk = child;
						break;
					}

					n -= size;
				}
			}
			return chunk.lines[n];
		},

		InitSyntax: function(name, syntax)
		{
			if (arguments.length > 2)
			{
				this.arSyntaxControl = [];
				for (var i = 2; i < arguments.length; ++i)
					this.arSyntaxControl.push(arguments[i]);
			}
			this.arSyntaxes[name] = syntax;
		},

		LoadSyntax: function()
		{
			this.oSyntax = Syntaxes[this.syntaxName]();
			this.oDoc.Iteration(0, this.oDoc.size, function (line){line.statusAfter = null;});
			this.work = [0];
			this.StartWorker();
		},

		StartWorker: function(time)
		{
			if (!this.work.length)
				return;

			this.highlight.set(time, this.Action(this.HighlightWorker));
		},

		HighlightWorker: function ()
		{
			var
				_this = this,
				task, start, status,
				unchanged, compare, realChange, i, bail,
				end = +new Date + this.timeout;

			while (this.work.length)
			{
				task = this.GetLine(this.showingFrom).statusAfter ? this.work.pop() : this.showingFrom;

				if (task >= this.oDoc.size)
					continue;

				start = this.FindStartLine(task);
				status = start && this.GetLine(start - 1).statusAfter;

				if (status)
					status = this.OnSyntaxCopyState(this.oSyntax, status);
				else
					status = this.OnSyntaxStartState(this.oSyntax);

				unchanged = 0;
				compare = this.oSyntax.compareStates;
				realChange = false;
				i = start;
				bail = false;

				this.oDoc.Iteration(i, this.oDoc.size, function (line)
				{
					var hadState = line.statusAfter;
					if (+new Date > end)
					{
						_this.work.push(i);
						_this.StartWorker(_this.delay);
						if (realChange)
							_this.arChanges.push({from: task, to: i + 1});
						return (bail = true);
					}

					var changed = line.highlight(_this.oSyntax, status, _this.tabSize);
					if (changed)
						realChange = true;

					line.statusAfter = _this.OnSyntaxCopyState(_this.oSyntax, status);

					var done = null;
					if (compare)
					{
						var same = hadState && compare(hadState, status);
						if (same != Pass)
							done = !!same;
					}

					if (done == null)
					{
						if (changed !== false || !hadState)
							unchanged = 0;
						else if (++unchanged > 3 && (!_this.oSyntax.Indent || _this.oSyntax.Indent(hadState, "") == _this.oSyntax.Indent(status, "")))
							done = true;
					}

					if (done)
						return true;
					i++;
				});

				if (bail)
					return;

				if (realChange)
					this.arChanges.push({from: task, to: i + 1});
			}
		},

		OnSyntaxStartState: function(syntax, a1, a2)
		{
			return syntax.startStatus ? syntax.startStatus(a1, a2) : true;
		},

		OnSyntaxCopyState: function(syntax, status)
		{
			if (status === true)
				return status;
			if (syntax.copyStatus)
				return syntax.copyStatus(status);

			var nstatus = {};
			for (var n in status)
			{
				var val = status[n];
				if (val instanceof Array)
					val = val.concat([]);
				nstatus[n] = val;
			}
			return nstatus;
		},

		FindStartLine: function(n)
		{
			var
				minindent,
				minline,
				search,
				lim = n - 40;

			for (search = n; search > lim; --search)
			{
				if (search == 0)
					return 0;
				var line = this.GetLine(search - 1);
				if (line.statusAfter)
					return search;
				var indented = line.indentation(this.tabSize);

				if (minline == null || minindent > indented)
				{
					minline = search - 1;
					minindent = indented;
				}
			}
			return minline;
		},

		GetStateBefore: function(n)
		{
			var
				start = this.FindStartLine(n),
				status = start && this.GetLine(start - 1).statusAfter;

			if (status)
				status = this.OnSyntaxCopyState(this.oSyntax, status);
			else
				status = this.OnSyntaxStartState(this.oSyntax);

			this.oDoc.Iteration(start, n, function (line)
			{
				line.highlight(_this.oSyntax, status, _this.tabSize);
				line.statusAfter = _this.OnSyntaxCopyState(_this.oSyntax, status);
			});

			if (start < n)
				this.arChanges.push({from: start, to: n});
			if (n < this.oDoc.size && !this.GetLine(n).statusAfter)
				this.work.push(n);

			return status;
		},

		UpdateDisplay: function(arChanges, suppressCallback, scrollTop)
		{
			if (!this.pScroller.clientWidth)
			{
				this.showingFrom = this.showingTo = this.displayOffset = 0;
				return;
			}

			var visible = this.VisibleLines(scrollTop);
			if (arChanges !== true && arChanges.length == 0 && visible.from > this.showingFrom && visible.to < this.showingTo)
				return this.UpdateVerticalScroll(scrollTop);

			var
				from = Math.max(visible.from - 100, 0),
				to = Math.min(this.oDoc.size, visible.to + 100);

			if (this.showingFrom < from && from - this.showingFrom < 20)
				from = this.showingFrom;
			if (this.showingTo > to && this.showingTo - to < 20)
				to = Math.min(this.oDoc.size, this.showingTo);

			var unChanged = arChanges === true ? [] : this.GetUnchanged([{from: this.showingFrom, to: this.showingTo, domStart: 0}], arChanges);
			var unChangedLines = 0;
			for (var i = 0; i < unChanged.length; ++i)
			{
				var range = unChanged[i];
				if (range.from < from)
				{
					range.domStart += (from - range.from);
					range.from = from;
				}
				if (range.to > to)
					range.to = to;

				if (range.from >= range.to)
					unChanged.splice(i--, 1);
				else
					unChangedLines += range.to - range.from;
			}

			if (unChangedLines == to - from && from == this.showingFrom && to == this.showingTo)
				return this.UpdateVerticalScroll(scrollTop);

			unChanged.sort(function (a, b){return a.domStart - b.domStart;});

			var
				th = this.TextHeight(),
				lineNumDisplay = this.pLineNum.style.display;

			this.pLineDiv.style.display = "none";
			this.PatchDisplay(from, to, unChanged);
			this.pLineDiv.style.display = this.pLineNum.style.display = "";

			var different = from != this.showingFrom || to != this.showingTo || this.lastSizeC != this.pScroller.clientHeight + th;
			if (different)
				this.lastSizeC = this.pScroller.clientHeight + th;

			this.showingFrom = from;
			this.showingTo = to;
			this.displayOffset = this.HeightAtLine(this.oDoc, from);

			if (this.pLineDiv.childNodes.length != this.showingTo - this.showingFrom)
			{
				throw new Error("BAD PATCH! " + JSON.stringify(unChanged) + " size=" + (this.showingTo - this.showingFrom) +
					" nodes=" + this.pLineDiv.childNodes.length);
			}

			this.pLineNum.style.display = lineNumDisplay;
			if (different || this.lineNumDirty)
				this.UpdateLineNum();

			this.UpdateVerticalScroll(scrollTop);
			this.UpdateSelection();

			return true;
		},

		VisibleLines: function(scrollTop)
		{
			var
				lh = this.TextHeight(),
				top = (scrollTop != null ? scrollTop : this.pScrollbar.scrollTop) - this.PaddingTop();

			return {
				from: this.LineAtHeight(this.oDoc, Math.max(0, Math.floor(top / lh))),
				to: this.LineAtHeight(this.oDoc, Math.ceil((top + this.pScroller.clientHeight) / lh))
			};
		},

		TextHeight: function()
		{
			var cnt = 50;
			if (!this._measurePre)
			{
				this._measurePre = BX.create("PRE");
				for (var i = 0; i < cnt; ++i)
				{
					this._measurePre.appendChild(document.createTextNode("W"));
					if (i < cnt - 1)
						this._measurePre.appendChild(BX.create("br"));
				}
			}
			var offsetHeight = this.pLineDiv.clientHeight;
			if (offsetHeight != this._cachedHeightFor)
			{
				this._cachedHeightFor = offsetHeight;
				BX.cleanNode(this.pMeasure);
				this.pMeasure.appendChild(this._measurePre.cloneNode(true));
				this._cachedHeight = this.pMeasure.firstChild.offsetHeight / cnt || 1;
				BX.cleanNode(this.pMeasure);
			}

			return this._cachedHeight;
		},


		UpdateLinesNoUndo: function(from, to, newText, selFrom, selTo)
		{
			if (this.suppressEdits)
				return;
			var
				_this = this,
				recomputeMaxLength = false,
				maxLineLength = this.maxLine.text.length;


			this.oDoc.Iteration(from.line, to.line + 1, function (line)
			{
				if (!line.hidden && line.text.length == maxLineLength)
				{
					recomputeMaxLength = true;
					return true;
				}
			});

			if (from.line != to.line || newText.length > 1)
				this.lineNumDirty = true;

			var
				nlines = to.line - from.line,
				firstLine = this.GetLine(from.line),
				lastLine = this.GetLine(to.line);

			if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "")
			{
				var
					added = [],
					prevLine = null;

				if (from.line)
				{
					prevLine = this.GetLine(from.line - 1);
					prevLine.fixMarkEnds(lastLine);
				}
				else
				{
					lastLine.fixMarkStarts();
				}

				for (var i = 0, e = newText.length - 1; i < e; ++i)
					added.push(JCLine.inheritMarks(newText[i], prevLine));

				if (nlines)
					this.oDoc.Remove(from.line, nlines, this.callbacks);
				if (added.length)
					this.oDoc.Insert(from.line, added);
			}
			else if (firstLine == lastLine)
			{
				if (newText.length == 1)
				{
					firstLine.Replace(from.ch, to.ch, newText[0]);
				}
				else
				{
					lastLine = firstLine.split(to.ch, newText[newText.length - 1]);
					firstLine.Replace(from.ch, null, newText[0]);
					firstLine.fixMarkEnds(lastLine);
					var added = [];
					for (var i = 1, e = newText.length - 1; i < e; ++i)
						added.push(JCLine.inheritMarks(newText[i], firstLine));
					added.push(lastLine);
					this.oDoc.Insert(from.line + 1, added);
				}
			}
			else if (newText.length == 1)
			{
				firstLine.Replace(from.ch, null, newText[0]);
				lastLine.Replace(null, to.ch, "");
				firstLine.append(lastLine);
				this.oDoc.Remove(from.line + 1, nlines, this.callbacks);
			}
			else
			{
				var added = [];
				firstLine.Replace(from.ch, null, newText[0]);
				lastLine.Replace(null, to.ch, newText[newText.length - 1]);
				firstLine.fixMarkEnds(lastLine);
				for (var i = 1, e = newText.length - 1; i < e; ++i)
					added.push(JCLine.inheritMarks(newText[i], firstLine));

				if (nlines > 1)
					this.oDoc.Remove(from.line + 1, nlines - 1, this.callbacks);
				this.oDoc.Insert(from.line + 1, added);
			}

			this.oDoc.Iteration(from.line, from.line + newText.length, function (line)
			{
				var l = line.text;
				if (!line.hidden && l.length > maxLineLength)
				{
					_this.maxLine = line;
					maxLineLength = l.length;
					_this.maxLineChanged = true;
					recomputeMaxLength = false;
				}
			});

			if (recomputeMaxLength)
				this.updateMaxLine = true;


			var newWork = [], lendiff = newText.length - nlines - 1;
			for (var i = 0, l = _this.work.length; i < l; ++i)
			{
				var task = _this.work[i];
				if (task < from.line)
					newWork.push(task);
				else if (task > to.line)
					newWork.push(task + lendiff);
			}

			var hlEnd = from.line + Math.min(newText.length, 500);
			this.HighlightLines(from.line, hlEnd);

			newWork.push(hlEnd);
			this.work = newWork;
			this.StartWorker(100);

			// Remember that these lines changed, for updating the display
			this.arChanges.push({from: from.line, to: to.line + 1, diff: lendiff});
			var changeObj = {
				from: from,
				to: to,
				text: newText
			};

			if (this.textChanged)
			{
				for (var cur = this.textChanged; cur.next; cur = cur.next){}
				cur.next = changeObj;
			}
			else
			{
				this.textChanged = changeObj;
			}

			// Update the selection
			function updateLine(n)
			{
				return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;
			}

			this.SetSelection(this.ClipPos(selFrom), this.ClipPos(selTo), updateLine(this.oSel.from.line),updateLine(this.oSel.to.line));
		},

		UpdateLines: function(from, to, newText, selFrom, selTo)
		{
			if (this.suppressEdits)
				return;

			if (this.oHistory)
			{
				var old = [];
				this.oDoc.Iteration(from.line, to.line + 1, function (line)
				{
					old.push(line.text);
				});
				this.oHistory.addChange(from.line, newText.length, old);
				while (this.oHistory.done.length > this.undoDepth)
					this.oHistory.done.shift();
			}
			this.UpdateLinesNoUndo(from, to, newText, selFrom, selTo);
		},

		HighlightLines: function(start, end)
		{
			var
				_this = this,
				status = this.GetStateBefore(start);

			this.oDoc.Iteration(start, end, function (line)
			{
				line.highlight(_this.oSyntax, status, _this.tabSize);
				line.statusAfter = _this.OnSyntaxCopyState(_this.oSyntax, status);
			});
		},

		SetSelectionUser: function(from, to)
		{
			var sh = this.shiftSelecting && this.ClipPos(this.shiftSelecting);
			if (sh)
			{
				if (this.PosLess(sh, from))
					from = sh;
				else if (this.PosLess(to, sh))
					to = sh;
			}
			this.SetSelection(from, to);
			this.userSelChange = true;
		},

		SetSelection: function(from, to, oldFrom, oldTo)
		{
			this.goalColumn = null;
			if (oldFrom == null)
			{
				oldFrom = this.oSel.from.line;
				oldTo = this.oSel.to.line;
			}

			if (this.PosEq(this.oSel.from, from) && this.PosEq(this.oSel.to, to))
				return;

			if (this.PosLess(to, from))
			{
				var tmp = to;
				to = from;
				from = tmp;
			}

			// Skip over hidden lines.
			if (from.line != oldFrom)
			{
				var from1 = this.SkipHidden(from, oldFrom, this.oSel.from.ch);
				if (!from1)
					this.SetLineHidden(from.line, false);
				else
					from = from1;
			}
			if (to.line != oldTo)
				to = this.SkipHidden(to, oldTo, this.oSel.to.ch);

			if (this.PosEq(from, to) || this.PosEq(from, this.oSel.to))
				this.oSel.inverted = false;
			else if (this.PosEq(to, this.oSel.from))
				this.oSel.inverted = true;

			if (this.PosEq(this.oSel.from, this.oSel.to))
			{
				var head = this.oSel.inverted ? from : to;
				if (head.line != this.oSel.from.line && this.oSel.from.line < this.oDoc.size)
				{
					var oldLine = this.GetLine(this.oSel.from.line);
					if (/^\s+$/.test(oldLine.text))
					{
						setTimeout(this.Action(function(){
							if (oldLine.parent && /^\s+$/.test(oldLine.text))
							{
								var no = lineNo(oldLine);
								this.ReplaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
							}
						}, this), 10);
					}
				}
			}

			this.oSel.from = from;
			this.oSel.to = to;
			this.selectionChanged = true;
		},

		PosEq: function(a, b)
		{
			return a.line == b.line && a.ch == b.ch;
		},

		PosLess: function(a, b)
		{
			return a.line < b.line || (a.line == b.line && a.ch < b.ch);
		},

		CopyPos: function(x)
		{
			return {line: x.line, ch: x.ch};
		},

		GetCharWidth: function()
		{
			if (this.pScroller.clientWidth == this._cachedWidthFor)
				return this._cachedWidth;
			this._cachedWidthFor = this.pScroller.clientWidth;

			var span = BX.create("SPAN", {text:" x"});
			BX.cleanNode(this.pMeasure);
			this.pMeasure.appendChild(BX.create("PRE", {children: [span]}));
			this._cachedWidth = span.offsetWidth || 10;

			return this._cachedWidth;
		},

		RestartBlink: function()
		{
			if (this._blinkerInterval)
				this._blinkerInterval = clearInterval(this._blinkerInterval);

			var
				cursor = this.pCursor,
				show = true;

			cursor.style.visibility = "";
			this._blinkerInterval = setInterval(function()
			{
				cursor.style.visibility = (show = !show) ? "" : "hidden";
			}, this.cursorInterval);
		},

		ReadInput: function()
		{
			if (!this.focused || this.HasSelection(this.pInput))
				return false;

			var text = this.pInput.value;
			if (text == this.prevInput)
				return false;

			this.shiftSelecting = null;
			var
				same = 0,
				l = Math.min(this.prevInput.length, text.length);

			while (same < l && this.prevInput[same] == text[same])
				++same;

			if (same < this.prevInput.length)
				this.oSel.from = {
					line: this.oSel.from.line,
					ch: this.oSel.from.ch - (this.prevInput.length - same)
				};
			this.ReplaceSelection(text.slice(same), "end");

			if (text.length > 1000)
				this.pInput.value = this.prevInput = "";
			else
				this.prevInput = text;

			return true;
		},

		ResetInput: function(user)
		{
			if (!this.PosEq(this.oSel.from, this.oSel.to))
			{
				this.prevInput = "";
				this.pInput.value = this.GetSelection();

				if (this.focused)
				{
					if (BX.browser.IsIOS())
					{
						this.pInput.selectionStart = 0;
						this.pInput.selectionEnd = this.pInput.value.length;
					}
					else
					{
						this.pInput.select();
					}
				}
			}
			else if (user)
			{
				this.prevInput = this.pInput.value = "";
			}
		},

		FocusInput: function()
		{
			BX.focus(this.pInput);
		},

		SetShift: function(val)
		{
			if (val)
				this.shiftSelecting = this.shiftSelecting || (this.oSel.inverted ? this.oSel.to : this.oSel.from);
			else
				this.shiftSelecting = null;
		},

		MakeTab: function(col)
		{
			var
				str,
				w = this.tabSize - col % this.tabSize;

			if (this._tabCache[w])
				return this._tabCache[w];

			for (str = "", i = 0; i < w; ++i)
				str += " ";

			this._tabCache[w] = {
				element: BX.create("TT", {text: str, props: {className: "bxce-tabspan"}}),
				width: w
			};

			return this._tabCache[w];
		},

		ComputeMaxLength: function()
		{
			this.maxLine = this.GetLine(0);
			this.maxLineChanged = true;
			var
				_this = this,
				maxLineLength = this.maxLine.text.length;


			this.oDoc.Iteration(1, this.oDoc.size, function (line)
			{
				var l = line.text;
				if (!line.hidden && l.length > maxLineLength)
				{
					maxLineLength = l.length;
					_this.maxLine = line;
				}
			});
			this.updateMaxLine = false;
		},

		MeasureLine: function(line, ch)
		{
			if (ch == 0)
				return {top: 0, left: 0};

			var pre = line.getElement(BX.proxy(this.MakeTab, this), ch, false);
			BX.cleanNode(this.pMeasure);
			this.pMeasure.appendChild(pre);
			var
				anchor = pre.anchor,
				top = anchor.offsetTop,
				left = anchor.offsetLeft;

//			if (BX.browser.IsIE() && top == 0 && left == 0)
//			{
//				var backup = BX.create("SPAN", {html:"x"});
//				anchor.parentNode.insertBefore(backup, anchor.nextSibling);
//				top = backup.offsetTop;
//			}

			return {top: top, left: left};
		},

		ScrollCursorIntoView: function()
		{
			var coords = this.CalculateCursorCoords();
			this.ScrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
			if (!this.focused)
				return;
			var
				box = this.pSizeCont.getBoundingClientRect(),
				doScroll = null;

			if (coords.y + box.top < 0)
				doScroll = true;
			else if (coords.y + box.top + this.TextHeight() > (window.innerHeight || document.documentElement.clientHeight))
				doScroll = false;

			if (doScroll != null)
			{
				var hidden = this.pCursor.style.display == "none";
				if (hidden)
				{
					this.pCursor.style.display = "";
					this.pCursor.style.left = coords.x + "px";
					this.pCursor.style.top = (coords.y - this.displayOffset) + "px";
				}
				this.pCursor.scrollIntoView(doScroll);
				if (hidden)
					this.pCursor.style.display = "none";
			}
		},

		CalculateCursorCoords: function()
		{
			var cur = this.LocalCoords(this.oSel.inverted ? this.oSel.from : this.oSel.to);
			return {x: cur.x, y: cur.y, yBot: cur.yBot};
		},

		ScrollIntoView: function(x1, y1, x2, y2)
		{
			var scrollPos = this.CalculateScrollPos(x1, y1, x2, y2);
			if (scrollPos.scrollLeft != null)
				this.pScroller.scrollLeft = scrollPos.scrollLeft;
			if (scrollPos.scrollTop != null)
				this.pScrollbar.scrollTop = this.pScroller.scrollTop = scrollPos.scrollTop;
		},

		CalculateScrollPos: function(x1, y1, x2, y2)
		{
			var
				pl = this.PaddingLeft(),
				pt = this.PaddingTop();

			y1 += pt;
			y2 += pt;
			x1 += pl;
			x2 += pl;

			var
				screen = this.pScroller.clientHeight,
				screentop = this.pScrollbar.scrollTop,
				result = {},
				docBottom = this.NeedsScrollbar() || Infinity,
				atTop = y1 < pt + 10,
				atBottom = y2 + pt > docBottom - 10,
				screenw = this.pScroller.clientWidth,
				screenleft = this.pScroller.scrollLeft,
				lineNumWidth = this.pLineNum.clientWidth,
				atLeft = x1 < lineNumWidth + pl + 10;

			if (y1 < screentop)
				result.scrollTop = atTop ? 0 : Math.max(0, y1);
			else if (y2 > screentop + screen)
				result.scrollTop = (atBottom ? docBottom : y2) - screen;

			if (x1 < screenleft + lineNumWidth || atLeft)
			{
				if (atLeft)
					x1 = 0;
				result.scrollLeft = Math.max(0, x1 - 10 - lineNumWidth);
			}
			else if (x2 > screenw + screenleft - 3)
			{
				result.scrollLeft = x2 + 10 - screenw;
			}

			return result;
		},

		LocalCoords: function(pos, inLineWrap)
		{
			var
				x,
				lh = this.TextHeight(),
				y = lh * (this.HeightAtLine(this.oDoc, pos.line) - (inLineWrap ? this.displayOffset : 0));

			if (pos.ch == 0)
				x = 0;
			else
				x = this.MeasureLine(this.GetLine(pos.line), pos.ch).left;

			return {x: x, y: y, yBot: y + lh};
		},

		CoordsChar: function(x, y)
		{
			var
				_this = this,
				th = this.TextHeight(),
				cw = this.GetCharWidth(),
				heightPos = this.displayOffset + Math.floor(y / th);

			if (heightPos < 0)
				return {line: 0, ch: 0};

			var lineNo = this.LineAtHeight(this.oDoc, heightPos);

			if (lineNo >= this.oDoc.size)
				return {
					line: this.oDoc.size - 1,
					ch: this.GetLine(this.oDoc.size - 1).text.length
				};

			var
				lineObj = this.GetLine(lineNo),
				text = lineObj.text,
				innerOff = 0;

			if (x <= 0 && innerOff == 0)
				return {
					line: lineNo,
					ch: 0
				};
			var wrongLine = false;

			function getX(len)
			{
				var sp = _this.MeasureLine(lineObj, len);
				return sp.left;
			}

			var from = 0, fromX = 0, to = text.length, toX;
			// Guess a suitable upper bound for our search.
			var estimated = Math.min(to, Math.ceil((x + innerOff * this.pScroller.clientWidth * .9) / cw));
			while (true)
			{
				var estX = getX(estimated);
				if (estX <= x && estimated < to)
				{
					estimated = Math.min(to, Math.ceil(estimated * 1.2));
				}
				else
				{
					toX = estX;
					to = estimated;
					break;
				}
			}
			if (x > toX)
				return {line: lineNo, ch: to};
			// Try to guess a suitable lower bound as well.
			estimated = Math.floor(to * 0.8);
			estX = getX(estimated);
			if (estX < x)
			{
				from = estimated;
				fromX = estX;
			}
			// Do a binary search between these bounds.
			while (true)
			{
				if (to - from <= 1)
				{
					var after = x - fromX < toX - x;
					return {line: lineNo, ch: after ? from : to, after: after};
				}
				var
					middle = Math.ceil((from + to) / 2),
					middleX = getX(middle);

				if (middleX > x)
				{
					to = middle;
					toX = middleX;
					if (wrongLine)
						toX += 1000;
				}
				else
				{
					from = middle;
					fromX = middleX;
				}
			}
		},

		UpdateVerticalScroll: function(scrollTop)
		{
			var _this = this;
			var scrollHeight = this.NeedsScrollbar();
			this.pScrollbar.style.display = scrollHeight ? "block" : "none";
			if (scrollHeight)
			{
				this.pScrollbarInner.style.height = this.pSizeCont.style.minHeight = scrollHeight + "px";
				this.pScrollbar.style.height = this.pScroller.clientHeight + "px";
				if (scrollTop != null)
				{
					this.pScrollbar.scrollTop = this.pScroller.scrollTop = scrollTop;
					// Chrome & Safari bug workaround
					if (BX.browser.IsSafari() || BX.browser.IsChrome())
					{
						setTimeout(function (){
							if (_this.pScrollbar.scrollTop != scrollTop)
								return;
							_this.pScrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1);
							_this.pScrollbar.scrollTop = scrollTop;
						}, 0);
					}
				}
			}
			else
			{
				this.pSizeCont.style.minHeight = "";
			}
			this.pMover.style.top = this.displayOffset * this.TextHeight() + "px";
		},

		NeedsScrollbar: function()
		{
			var realHeight = this.oDoc.size * this.TextHeight() + 2 * this.PaddingTop();
			return realHeight * .99 > this.pScroller.offsetHeight ? realHeight : false;
		},

		PaddingTop: function()
		{
			return this.pLinesCont.offsetTop;
		},

		PaddingLeft: function()
		{
			return this.pLinesCont.offsetLeft;
		},

		PosFromMouse: function(e, liberal)
		{
			var
				offW = GetOffset(this.pScroller, true),
				x = e.clientX,
				y = e.clientY;

			if (!liberal && (x - offW.left > this.pScroller.clientWidth || y - offW.top > this.pScroller.clientHeight))
				return null;

			var offL = GetOffset(this.pLinesCont, true);
			return this.CoordsChar(x - offL.left, y - offL.top);
		},

		LineAtHeight: function(chunk, h)
		{
			var n = 0;
			outer: do {
				for (var i = 0, e = chunk.children.length; i < e; ++i)
				{
					var child = chunk.children[i], ch = child.height;
					if (h < ch)
					{
						chunk = child;
						continue outer;
					}
					h -= ch;
					n += child.GetSize();
				}
				return n;
			}
			while (!chunk.lines);

			for (var i = 0; i < chunk.lines.length; ++i)
			{
				var
					line = chunk.lines[i],
					lh = line.height;
				if (h < lh)
					break;
				h -= lh;
			}
			return n + i;
		},

		HeightAtLine: function(chunk, n)
		{
			var h = 0;
			outer: do {
				for (var i = 0, e = chunk.children.length; i < e; ++i)
				{
					var child = chunk.children[i], sz = child.GetSize();
					if (n < sz)
					{
						chunk = child;
						continue outer;
					}
					n -= sz;
					h += child.height;
				}
				return h;
			}
			while (!chunk.lines);

			for (var i = 0; i < n; ++i)
				h += chunk.lines[i].height;

			return h;
		},

		GetUnchanged: function(unChanged, arChanges)
		{
			var
				change, unChanged2, diff, range,
				i, l = arChanges.length || 0, j;

			for (i = 0; i < l; ++i)
			{
				change = arChanges[i];
				unChanged2 = [];
				diff = change.diff || 0;

				for (j = 0; j < unChanged.length; ++j)
				{
					range = unChanged[j];
					if (change.to <= range.from && change.diff)
					{
						unChanged2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
					}
					else if (change.to <= range.from || change.from >= range.to)
					{
						unChanged2.push(range);
					}
					else
					{
						if (change.from > range.from)
							unChanged2.push({from: range.from, to: change.from, domStart: range.domStart});

						if (change.to < range.to)
							unChanged2.push({from: change.to + diff, to: range.to + diff, domStart: range.domStart + (change.to - range.from)});
					}
				}
				unChanged = unChanged2;
			}
			return unChanged;
		},

		PatchDisplay: function(from, to, unChanged)
		{
			function killNode(node)
			{
				var tmp = node.nextSibling;
				node.parentNode.removeChild(node);
				return tmp;
			}

			if (!unChanged.length)
			{
				BX.cleanNode(this.pLineDiv)
			}
			else
			{
				var
					domPos = 0, i, cur, j, e,
					curNode = this.pLineDiv.firstChild, n;

				for (i = 0; i < unChanged.length; ++i)
				{
					cur = unChanged[i];
					while (cur.domStart > domPos)
					{
						curNode = killNode(curNode);
						domPos++;
					}

					for (j = 0, e = cur.to - cur.from; j < e; ++j)
					{
						curNode = curNode.nextSibling;
						domPos++;
					}
				}

				while (curNode)
					curNode = killNode(curNode);
			}

			// This pass fills in the lines that actually changed.
			var
				_this = this,
				nextIntact = unChanged.shift(),
				j = from;

			curNode = this.pLineDiv.firstChild;
			this.oDoc.Iteration(from, to, function (line)
			{
				var pLine;
				if (nextIntact && nextIntact.to == j)
					nextIntact = unChanged.shift();
				if (!nextIntact || nextIntact.from > j)
				{
					if (line.hidden)
					{
						pLine = BX.create("PRE");
					}
					else
					{
						pLine = line.getElement(BX.proxy(_this.MakeTab, _this));
						if (line.className)
							pLine.className = line.className;
						if (line.bgClassName)
							pLine = BX.create("DIV", {children:
								[
									BX.create("PRE", {html:"\u00a0", props:{className: line.bgClassName}, style:{position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, zIndex: -2}}),
									pLine
								], style: {position: 'relative'}});
					}
					_this.pLineDiv.insertBefore(pLine, curNode);
				}
				else
				{
					curNode = curNode.nextSibling;
				}
				++j;
			});
		},

		UpdateLineNum: function()
		{
			var
				hText = this.pMover.offsetHeight,
				hEditor = this.pScroller.clientHeight,
				frag = document.createDocumentFragment(),
				i = this.showingFrom,
				normalNode;

			this.pLineNum.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";

			this.oDoc.Iteration(this.showingFrom, Math.max(this.showingTo, this.showingFrom + 1), function (line)
			{
				if (line.hidden)
				{
					frag.appendChild(BX.create("PRE"));
				}
				else
				{
					var pLine = frag.appendChild(BX.create("PRE", {html: i + 1}));
					for (var j = 1; j < line.height; ++j)
					{
						pLine.appendChild(BX.create("BR"));
						pLine.appendChild(document.createTextNode("\u00a0"));
					}
					normalNode = i;
				}
				++i;
			});
			this.pLineNum.style.display = "none";

			// One more line
//			frag.appendChild(BX.create("PRE", {html: i}));
//			normalNode = i;

			BX.cleanNode(this.pLineNumText);
			this.pLineNumText.appendChild(frag);

			if (normalNode != null)
			{
				var
					val = '',
					node = this.pLineNumText.childNodes[normalNode - this.showingFrom],
					minwidth = String(this.oDoc.size).length,
					ch = node.firstChild,
					pad = "";

				if (ch)
					val = ch.textContent || ch.innerText || ch.nodeValue || '';

				while (val.length + pad.length < minwidth)
					pad += "\u00a0";

				if (pad)
					node.insertBefore(document.createTextNode(pad), node.firstChild);
			}
			this.pLineNum.style.display = "";

			var resized = Math.abs((parseInt(this.pLinesCont.style.marginLeft) || 0) - this.pLineNum.offsetWidth) > 2;

			this.pLinesCont.style.marginLeft = this.pLineNum.offsetWidth + "px";

			this.lineNumDirty = false;
			return resized;
		},

		UpdateSelection: function()
		{
			var
				collapsed = this.PosEq(this.oSel.from, this.oSel.to),
				fromPos = this.LocalCoords(this.oSel.from, true),
				toPos = collapsed ? fromPos : this.LocalCoords(this.oSel.to, true),
				headPos = this.oSel.inverted ? fromPos : toPos,
				th = this.TextHeight(),
				wrapOff = GetOffset(this.pInnerContHL),
				lineOff = GetOffset(this.pLineDiv);

			this.pInputCont.style.top = Math.max(0, Math.min(this.pScroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
			this.pInputCont.style.left = Math.max(0, Math.min(this.pScroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";

			if (collapsed)
			{
				this.pCursor.style.top = headPos.y + "px";
				this.pCursor.style.left = headPos.x + "px";
				this.pCursor.style.display = "";
				this.pSelectionDiv.style.display = "none";
			}
			else
			{
				var
					sameLine = fromPos.y == toPos.y,
					frag = document.createDocumentFragment(),
					clientWidth = this.pLinesCont.clientWidth || this.pLinesCont.offsetWidth,
					clientHeight = this.pLinesCont.clientHeight || this.pLinesCont.offsetHeight;

				function add(left, top, right, height)
				{
					var pEl = frag.appendChild(BX.create("DIV", {props: {className: "bxce-selected"}, style:{position: "absolute", left: left + "px", top: top + "px", height: height + "px"}}));

//					if (BX.browser.IsDoctype())
					pEl.style.width = (right ? (clientWidth - right - left) : clientWidth) + "px";
//					else
//						pEl.style.right = right + "px";

				};

				if (this.oSel.from.ch && fromPos.y >= 0)
				{
					var right = sameLine ? clientWidth - toPos.x : 0;
					add(fromPos.x, fromPos.y, right, th);
				}

				var middleStart = Math.max(0, fromPos.y + (this.oSel.from.ch ? th : 0));
				var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;

				if (middleHeight > 0.2 * th)
					add(0, middleStart, 0, middleHeight);
				if ((!sameLine || !this.oSel.from.ch) && toPos.y < clientHeight - .5 * th)
					add(0, toPos.y, clientWidth - toPos.x, th);

				BX.cleanNode(this.pSelectionDiv);
				this.pSelectionDiv.appendChild(frag);

				this.pCursor.style.display = "none";
				this.pSelectionDiv.style.display = "";
			}
		},

		// *****  Event handlers *****
		OnScrollBar: function(e)
		{
			if (this.pScrollbar.scrollTop != this._lastScrollTop)
			{
				this._lastScrollTop = this.pScroller.scrollTop = this.pScrollbar.scrollTop;
				this.UpdateDisplay([]);
			}
		},

		OnScrollMain: function(e)
		{
			if (this.pLineNum.style.left != this.pScroller.scrollLeft + "px")
				this.pLineNum.style.left = this.pScroller.scrollLeft + "px";

			if (this.pScroller.scrollTop != this._lastScrollTop)
			{
				this._lastScrollTop = this.pScroller.scrollTop;
				if (this.pScrollbar.scrollTop != this._lastScrollTop)
					this.pScrollbar.scrollTop = this._lastScrollTop;
				this.UpdateDisplay([]);
			}
		},

		OnMouseDown: function(e)
		{
			if (!e)
				e = window.event;

			if (!this.highlightMode)
				return;

			this.SetShift(e.shiftKey);
			var
				n,
				target = e.target || e.srcElement;

			for (n = target; n != this.pInnerContHL; n = n.parentNode)
				if (n.parentNode == this.pSizeCont && n != this.pMover)
					return;

			for (n = target; n != this.pInnerContHL; n = n.parentNode)
				if (n.parentNode == this.pLineNumText)
					return BX.PreventDefault(e);
			//return e_preventDefault(e);

			var start = this.PosFromMouse(e);
			if (mouseButton(e) == 2)
			{
				if (start)
					this.SetCursor(start.line, start.ch, true);
				setTimeout(BX.proxy(this.FocusInput, this), 20);
				return BX.PreventDefault(e);
				//e_preventDefault(e);
				//return;
			}

			if (!start)
			{
				if (target == this.pScroller)
					BX.PreventDefault(e);
				//e_preventDefault(e);
				return;
			}

			if (!this.focused)
				this.OnFocus();

			var now = +new Date;

			this._type = "single";
			if (this.lastDoubleClick && this.lastDoubleClick.time > now - 400 && this.PosEq(this.lastDoubleClick.pos, start))
			{
				this._type = "triple";
				BX.PreventDefault(e);
				//e_preventDefault(e);
				setTimeout(BX.proxy(this.FocusInput, this), 20);
				this.SelectLine(start.line);
			}
			else if (this.lastClick && this.lastClick.time > now - 400 && this.PosEq(this.lastClick.pos, start))
			{
				this._type = "double";
				this.LastDoubleClick = {time: now, pos: start};
				BX.PreventDefault(e);
				//e_preventDefault(e);
				var word = this.FindWordAt(start);
				this.SetSelectionUser(word.from, word.to);
			}
			else
			{
				this.lastClick = {time: now, pos: start};
			}

			this._selectingTimeout = null;
			this._last = start;
			this._start = start;

			BX.PreventDefault(e);
			//e_preventDefault(e);
			if (this._type == "single")
				this.SetCursor(start.line, start.ch, true);

			this._startstart = this.oSel.from;
			this._startend = this.oSel.to;

			BX.bind(document, "mousemove", BX.proxy(this.OnMouseMove, this));
			BX.bind(document, "mouseup", BX.proxy(this._OnDone, this));
		},

		_DoSelect: function(cur)
		{
			if (this._type == "single")
			{
				this.SetSelectionUser(this._start, cur);
			}
			else if (this._type == "double")
			{
				var word = this.FindWordAt(cur);
				if (this.PosLess(cur, this._startstart))
					this.SetSelectionUser(word.from, this._startend);
				else
					this.SetSelectionUser(this._startstart, word.to);
			}
			else if (this._type == "triple")
			{
				if (this.PosLess(cur, this._startstart))
					this.SetSelectionUser(this._startend, this.ClipPos({line: cur.line, ch: 0}));
				else
					this.SetSelectionUser(this._startstart, this.ClipPos({line: cur.line + 1, ch: 0}));
			}
		},

		OnMouseMove: function(e)
		{
			this.OnBeforeAction();
			if (this._selectingTimeout)
				this._selectingTimeout = clearTimeout(this._selectingTimeout);
			//e_preventDefault(e);
			BX.PreventDefault(e);
			if (!BX.browser.IsIE() && !mouseButton(e))
				this._OnDone(e);
			else
				this._OnExtend(e);
			this.OnAfterAction();
		},

		_OnDone: function(e)
		{
			if (this._selectingTimeout)
				this._selectingTimeout = clearTimeout(this._selectingTimeout);

			var cur = this.PosFromMouse(e);
			if (cur)
				this._DoSelect(cur);

			BX.PreventDefault(e);
			this.FocusInput();

			this.updateInput = true;
			//if (this.selectionChanged)
				this.ResetInput(this.userSelChange);

			BX.unbind(document, "mousemove", BX.proxy(this.OnMouseMove, this));
			BX.unbind(document, "mouseup", BX.proxy(this._OnDone, this));
		},

		_OnExtend: function(e)
		{
			var
				_this = this,
				cur = this.PosFromMouse(e, true);

			if (cur && !this.PosEq(cur, this._last))
			{
				if (!this.focused)
					this.OnFocus();
				this._last = cur;
				this._DoSelect(cur);
				this.updateInput = false;
				var visible = this.VisibleLines();
				if (cur.line >= visible.to || cur.line < visible.from)
					this._selectingTimeout = setTimeout(this.Action(function(){_this._OnExtend(e)}, this), 150);
			}
		},


		OnDoubleClick: function(e)
		{
//			for (var n = e_target(e); n != this.pInnerContHL; n = n.parentNode)
//				if (n.parentNode == this.pLineNumText)
//					return BX.PreventDefault(e);
//					//return e_preventDefault(e);
//			e_preventDefault(e);
			return BX.PreventDefault(e);
		},

		DoHandleBinding: function(bound, dropShift)
		{
			if (typeof bound == "string")
			{
				bound = this.GetCommand(bound);
				if (!bound)
					return false;
			}

			var prevShift = this.shiftSelecting;
			try
			{
				if (dropShift)
					this.suppressEdits = null;
				bound();
			}
			catch (e)
			{
				if (e != Pass)
					throw e;
				return false;
			}
			finally
			{
				this.shiftSelecting = prevShift;
				this.suppressEdits = false;
			}
			return true;
		},

		HandleKeyBinding: function(e)
		{
			var
				_this = this,
				name = this.keyNames[e.keyCode],
				handled = false;

			if (name == null || e.altGraphKey)
				return false;
			if (e.altKey)
				name = "Alt-" + name;
			if (e.ctrlKey)
				name = "Ctrl-" + name;
			if (e.metaKey)
				name = "Cmd-" + name;

			var stopped = false;
			function stop()
			{
				stopped = true;
			}

			if (e.shiftKey)
			{
				handled = this.LookupKey(
					"Shift-" + name,
					function(b){return _this.DoHandleBinding(b, true);},
					stop
				)
					||
					this.LookupKey(
						name,
						function (b){
							if (typeof b == "string" && /^go[A-Z]/.test(b))
								return _this.DoHandleBinding(b);
						},
						stop
					);
			}
			else
			{
				handled = this.LookupKey(name, BX.proxy(this.DoHandleBinding, this), stop);
			}

			if (stopped)
				handled = false;

			if (handled)
			{
				//e_preventDefault(e);
				BX.PreventDefault(e);
				this.RestartBlink();
				if (BX.browser.IsIE())
				{
					e.oldKeyCode = e.keyCode;
					e.keyCode = 0;
				}
			}
			return handled;
		},

		HandleCharBinding: function(e, ch)
		{
			var _this = this;
			var handled = this.LookupKey("'" + ch + "'", function (b){return _this.DoHandleBinding(b, true);});

			if (handled)
			{
				//e_preventDefault(e);
				BX.PreventDefault(e);
				_this.RestartBlink();
			}
			return handled;
		},

		OnKeyDown: function(e)
		{
			if (!this.focused)
				this.OnFocus();

			if (BX.browser.IsIE() && e.keyCode == 27)
				e.returnValue = false;

			if (this.pollingFast && this.ReadInput())
				this.pollingFast = false;

			var code = e.keyCode;

			// IE does strange things with escape.
			this.SetShift(code == 16 || e.shiftKey);
			var handled = this.HandleKeyBinding(e);

			if (BX.browser.IsOpera())
			{
				this._lastStoppedKey = handled ? code : null;
				if (!handled && code == 88 && e.ctrlKey)
					this.ReplaceSelection("");
			}
		},

		OnKeyPress: function(e)
		{
			if (this.pollingFast)
				this.ReadInput();

			var
				keyCode = e.keyCode,
				charCode = e.charCode;

			if (BX.browser.IsOpera() && keyCode == this._lastStoppedKey)
			{
				this._lastStoppedKey = null;
				//e_preventDefault(e);
				return BX.PreventDefault(e);
				//return;
			}

			if (BX.browser.IsOpera() && (!e.which || e.which < 10) && this.HandleKeyBinding(e))
				return;

			var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
			if (this.oSyntax.magicSym && this.oSyntax.magicSym.indexOf(ch) > -1)
				setTimeout(this.Action(function(){this.IndentLine(this.oSel.to.line, "smart");}, this), 75);

			if (this.HandleCharBinding(e, ch))
				return;
			this.FastPoll();
		},

		OnKeyUp: function(e)
		{
			if (e.keyCode == 16)
				this.shiftSelecting = null;
		},

		OnFocus: function()
		{
			if (!this.focused)
			{
				this.focused = true;
				BX.addClass(this.pScroller, "bxce-focused");
				this.ResetInput(true);
			}
			this.SlowPoll();
			this.RestartBlink();
		},

		OnBlur: function()
		{
			this.Save();

			var _this = this;
			if (this.focused)
			{
				this.focused = false;
				if (this.bracketHighlighted)
				{
					this.Action(function()
					{
						if (this.bracketHighlighted)
						{
							this.bracketHighlighted();
							this.bracketHighlighted = null;
						}
					}, this);
				}
				BX.removeClass(this.pScroller, "bxce-focused");
			}

			if (this._blinkerInterval)
				this._blinkerInterval = clearInterval(this._blinkerInterval);

			setTimeout(function ()
			{
				if (!_this.focused)
					_this.shiftSelecting = null;
			}, 150);
		},
		// END *****  Event handlers *****

		ReplaceRange: function(code, from, to)
		{
			var _this = this;
			from = this.ClipPos(from);
			to = to ? this.ClipPos(to) : from;
			code = this.ExplodeLines(code);

			function adjustPos(pos)
			{
				if (_this.PosLess(pos, from))
					return pos;
				if (!_this.PosLess(to, pos))
					return end;
				var line = pos.line + code.length - (to.line - from.line) - 1;
				var ch = pos.ch;
				if (pos.line == to.line)
					ch += code[code.length - 1].length - (to.ch - (to.line == from.line ? from.ch : 0));

				return {line: line, ch: ch};
			}

			var end;
			this.ReplaceRange1(code, from, to, function (end1)
			{
				end = end1;
				return {from: adjustPos(_this.oSel.from), to: adjustPos(_this.oSel.to)};
			});

			return end;
		},

		ReplaceSelection: function(code, collapse)
		{
			var _this = this;
			this.ReplaceRange1(this.ExplodeLines(code), this.oSel.from, this.oSel.to, function (end)
			{
				if (collapse == "end")
					return {from: end, to: end};
				else if (collapse == "start")
					return {from: _this.oSel.from, to: _this.oSel.from};
				else
					return {from: _this.oSel.from, to: end};
			});
		},

		ReplaceRange1: function(code, from, to, computeSel)
		{
			var
				endch = code.length == 1 ? code[0].length + from.ch : code[code.length - 1].length,
				newSel = computeSel({line: from.line + code.length - 1, ch: endch});

			this.UpdateLines(from, to, code, newSel.from, newSel.to);
		},

		GetRange: function(from, to, lineSep)
		{
			var
				l1 = from.line,
				l2 = to.line;
			if (l1 == l2)
				return this.GetLine(l1).text.slice(from.ch, to.ch);

			var code = [this.GetLine(l1).text.slice(from.ch)];
			this.oDoc.Iteration(l1 + 1, l2, function (line){code.push(line.text);});
			code.push(this.GetLine(l2).text.slice(0, to.ch));
			return code.join(lineSep || "\n");
		},

		GetSelection: function (lineSep)
		{
			return this.GetRange(this.oSel.from, this.oSel.to, lineSep);
		},

		SlowPoll: function()
		{
			if (!this.pollingFast)
				this.pollDelayed.set(this.pollInterval, BX.proxy(this.OnSlowPoolRun, this));
		},

		OnSlowPoolRun: function()
		{
			if (!this.highlightMode)
				return BX.DoNothing;

			this.OnBeforeAction();
			this.ReadInput();
			if (this.focused)
				this.SlowPoll();
			this.OnAfterAction();
		},

		FastPoll: function()
		{
			var
				_this = this,
				missed = false;
			this.pollingFast = true;

			function p()
			{
				_this.OnBeforeAction();
				var changed = _this.ReadInput();
				if (!changed && !missed)
				{
					missed = true;
					_this.pollDelayed.set(60, p);
				}
				else
				{
					_this.pollingFast = false;
					_this.SlowPoll();
				}
				_this.OnAfterAction();
			}

			this.pollDelayed.set(20, p);
		},

		ChangeLine: function(handle, op)
		{
			var no = handle, line = handle;
			if (typeof handle == "number")
				line = this.GetLine(this.ClipLine(handle));
			else
				no = this.LineNo(handle);

			if (no == null)
				return null;

			if (op(line, no))
				this.arChanges.push({from: no, to: no + 1});
			else
				return null;
			return line;
		},

		SetLineHidden: function(handle, hidden)
		{
			var _this = this;
			return this.ChangeLine(handle, function (line, no)
			{
				if (line.hidden != hidden)
				{
					line.hidden = hidden;

					if (hidden && line.text.length == this.maxLine.text.length)
					{
						this.updateMaxLine = true;
					}
					else if (!hidden && line.text.length > this.maxLine.text.length)
					{
						this.maxLine = line;
						this.updateMaxLine = false;
					}

					_this.UpdateLineHeight(line, hidden ? 0 : 1);
					var
						fline = this.oSel.from.line,
						tline = this.oSel.to.line;

					if (hidden && (fline == no || tline == no))
					{
						var from = fline == no ? _this.SkipHidden({line: fline, ch: 0}, fline, 0) : this.oSel.from;
						var to = tline == no ? _this.SkipHidden({line: tline, ch: 0}, tline, 0) : this.oSel.to;
						if (!to) // Can't hide the last visible line, we'd have no place to put the cursor
							return;
						_this.SetSelection(from, to);
					}
					return (_this.lineNumDirty = true);
				}
			});
		},

		UpdateLineHeight: function(line, height)
		{
			this.lineNumDirty = true;
			var diff = height - line.height;
			for (var n = line; n; n = n.parent)
				n.height += diff;
		},


		SkipHidden: function(pos, oldLine, oldCh)
		{
			var _this = this;
			function getNonHidden(dir)
			{
				var lNo = pos.line + dir, end = dir == 1 ? _this.oDoc.size : -1;
				while (lNo != end)
				{
					var line = getLine(lNo);
					if (!line.hidden)
					{
						var ch = pos.ch;
						if (toEnd || ch > oldCh || ch > line.text.length)
							ch = line.text.length;
						return {line: lNo, ch: ch};
					}
					lNo += dir;
				}
			}

			var
				line = this.GetLine(pos.line),
				toEnd = pos.ch == line.text.length && pos.ch != oldCh;

			if (!line.hidden)
				return pos;

			if (pos.line >= oldLine)
				return getNonHidden(1) || getNonHidden(-1);
			else
				return getNonHidden(-1) || getNonHidden(1);
		},

		SelectLine: function(line)
		{
			this.SetSelectionUser({line: line, ch: 0}, this.ClipPos({line: line + 1, ch: 0}));
		},

		IndentSelected: function(syntax)
		{
			if (this.PosEq(this.oSel.from, this.oSel.to))
				return this.IndentLine(this.oSel.from.line, syntax);
			var e = this.oSel.to.line - (this.oSel.to.ch ? 0 : 1);
			for (var i = this.oSel.from.line; i <= e; ++i)
				this.IndentLine(i, syntax);
		},

		IndentLine: function(n, how)
		{
			if (!how)
				how = "add";

			if (how == "smart")
			{
				if (!this.oSyntax.Indent)
					how = "prev";
				else
					var status = this.GetStateBefore(n);
			}

			var
				line = this.GetLine(n),
				curSpace = line.indentation(this.tabSize),
				curSpaceString = line.text.match(/^\s*/)[0],
				indentation;

			if (how == "smart")
			{
				indentation = this.oSyntax.Indent(status, line.text.slice(curSpaceString.length), line.text);
				if (indentation == Pass)
					how = "prev";
			}

			if (how == "prev")
				indentation = n ? this.GetLine(n - 1).indentation(this.tabSize) : 0;
			else if (how == "add")
				indentation = curSpace + this.indentUnit;
			else if (how == "subtract")
				indentation = curSpace - this.indentUnit;

			indentation = Math.max(0, indentation);
			var
				i,
				indentString = "",
				pos = 0;

			for (i = Math.floor(indentation / this.tabSize); i; --i)
			{
				pos += this.tabSize;
				indentString += "\t";
			}

			while (pos < indentation)
			{
				++pos;
				indentString += " ";
			}

			if (indentString != curSpaceString)
				this.ReplaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
		},

		FindPosH: function(dir, unit)
		{
			var
				_this = this,
				end = this.oSel.inverted ? this.oSel.from : this.oSel.to,
				line = end.line,
				ch = end.ch,
				lineObj = this.GetLine(line);

			function findNextLine()
			{
				var l, lo, e;
				for (l = line + dir, e = dir < 0 ? -1 : _this.oDoc.size; l != e; l += dir)
				{
					lo = _this.GetLine(l);
					if (!lo.hidden)
					{
						line = l;
						lineObj = lo;
						return true;
					}
				}
				return false;
			}

			function moveOnce(boundToLine)
			{
				if (ch == (dir < 0 ? 0 : lineObj.text.length))
				{
					if (!boundToLine && findNextLine())
						ch = dir < 0 ? lineObj.text.length : 0;
					else
						return false;
				}
				else
				{
					ch += dir;
				}
				return true;
			}

			if (unit == "char")
			{
				moveOnce();
			}
			else if (unit == "column")
			{
				moveOnce(true);
			}
			else if (unit == "word")
			{
				var sawWord = false;
				for (; ;)
				{
					if (dir < 0) if (!moveOnce()) break;
					if (isWordChar(lineObj.text.charAt(ch)))
					{
						sawWord = true;
					}
					else if (sawWord)
					{
						if (dir < 0)
						{
							dir = 1;
							moveOnce();
						}
						break;
					}

					if (dir > 0 && !moveOnce())
						break;
				}
			}
			return {line: line, ch: ch};
		},

		MoveH: function(dir, unit)
		{
			var pos = dir < 0 ? this.oSel.from : this.oSel.to;
			if (this.shiftSelecting || this.PosEq(this.oSel.from, this.oSel.to))
				pos = this.FindPosH(dir, unit);

			this.SetCursor(pos.line, pos.ch, true);
		},

		DeleteH: function(dir, unit)
		{
			if (!this.PosEq(this.oSel.from, this.oSel.to))
				this.ReplaceRange("", this.oSel.from, this.oSel.to);
			else if (dir < 0)
				this.ReplaceRange("", this.FindPosH(dir, unit), this.oSel.to);
			else
				this.ReplaceRange("", this.oSel.from, this.FindPosH(dir, unit));

			this.userSelChange = true;
		},

		MoveV: function(dir, unit)
		{
			var
				screen, target, th,
				pos = this.LocalCoords(this.oSel.inverted ? this.oSel.from : this.oSel.to, true);

			if (this.goalColumn != null)
				pos.x = this.goalColumn;

			if (unit == "page")
			{
				screen = Math.min(this.pScroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
				target = this.CoordsChar(pos.x, pos.y + screen * dir);
			}
			else if (unit == "line")
			{
				th = this.TextHeight();
				target = this.CoordsChar(pos.x, pos.y + .5 * th + dir * th);
			}

			if (unit == "page")
				this.pScrollbar.scrollTop += this.LocalCoords(target, true).y - pos.y;
			this.SetCursor(target.line, target.ch, true);
			this.goalColumn = pos.x;
		},

		FindWordAt: function(pos)
		{
			var
				line = this.GetLine(pos.line).text,
				start = pos.ch,
				end = pos.ch;

			if (line)
			{
				if (pos.after === false || end == line.length)
					--start;
				else
					++end;

				var
					startChar = line.charAt(start),
					check = isWordChar(startChar) ? isWordChar : /\s/.test(startChar) ? function (ch){return /\s/.test(ch);} : function (ch){return !/\s/.test(ch) && !isWordChar(ch);};

				while (start > 0 && check(line.charAt(start - 1)))
					--start;
				while (end < line.length && check(line.charAt(end)))
					++end;
			}
			return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
		},


		// Key mapping
		InitKeyEngine: function()
		{
			this.Map = {
				"Left": "goCharLeft",
				"Right": "goCharRight",
				"Up": "goLineUp",
				"Down": "goLineDown",
				"End": "goLineEnd",
				"Home": "goHome",
				"PageUp": "goPageUp", "PageDown": "goPageDown",
				"Delete": "delCharRight",
				"Backspace": "delCharLeft",
				"Tab": "tab",
				"Shift-Tab": "indentLess",
				"Enter": "newlineAndIndent",
				"Insert": "toggleOverwrite"
			};
			if(BX.browser.IsMac())
			{
				this.Map["Cmd-A"] = "selectAll";
				this.Map["Cmd-D"] = "deleteLine";
				this.Map["Cmd-Z"] = "undo";
				//this.Map["Shift-Cmd-Z"] = "redo";
				//this.Map["Cmd-Y"] = "redo";
				this.Map["Cmd-Up"] = "goDocStart";
				this.Map["Cmd-End"] = "goDocEnd";
				this.Map["Cmd-Down"] = "goDocEnd";
				this.Map["Alt-Left"] = "goWordLeft";
				this.Map["Alt-Right"] = "goWordRight";
				this.Map["Cmd-Left"] = "goLineStart";
				this.Map["Cmd-Right"] = "goLineEnd";
				this.Map["Alt-Backspace"] = "delWordLeft";
				this.Map["Ctrl-Alt-Backspace"] = "delWordRight";
				this.Map["Alt-Delete"] = "delWordRight";
			}
			else
			{
				this.Map["Ctrl-A"] = "selectAll";
				this.Map["Ctrl-D"] = "deleteLine";
				this.Map["Ctrl-Z"] = "undo";
				//this.Map["Shift-Ctrl-Z"] = "redo";
				//this.Map["Ctrl-Y"] = "redo";
				this.Map["Ctrl-Home"] = "goDocStart";
				this.Map["Alt-Up"] = "goDocStart";
				this.Map["Ctrl-End"] = "goDocEnd";
				this.Map["Ctrl-Down"] = "goDocEnd";
				this.Map["Ctrl-Left"] = "goWordLeft";
				this.Map["Ctrl-Right"] = "goWordRight";
				this.Map["Alt-Left"] = "goLineStart";
				this.Map["Alt-Right"] = "goLineEnd";
				this.Map["Ctrl-Backspace"] = "delWordLeft";
				this.Map["Ctrl-Delete"] = "delWordRight";
			}

			this.keyNames = {3: "Enter",8: "Backspace",9: "Tab",13: "Enter",16: "Shift",17: "Ctrl",18: "Alt",19: "Pause",20: "CapsLock",27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};

			var i;
			for (i = 0; i < 10; i++) // Number keys
				this.keyNames[i + 48] = String(i);

			for (i = 65; i <= 90; i++) // Alphabetic keys
				this.keyNames[i] = String.fromCharCode(i);

			for (i = 1; i <= 12; i++) // Function keys
				this.keyNames[i + 111] = this.keyNames[i + 63235] = "F" + i;
		},

		LookupKey: function(name, handle, stop)
		{
			var found = this.Map[name];
			if (found === false)
			{
				if (stop && typeof stop == 'function')
					stop();
				return true;
			}

			return found != null && handle(found);
		},

		GetCommand: function(command)
		{
			return {
				selectAll: BX.proxy(function(){this.DoSelection({line: 0, ch: 0}, {line: this.oDoc.size - 1});}, this),
				killLine: BX.proxy(function(){
					var
						from = this.GetCursor(true),
						to = this.GetCursor(false),
						sel = !this.PosEq(from, to);

					if (!sel && this.GetLineText(from.line).length == from.ch)
						this.ReplaceRange("", from, {line: from.line + 1, ch: 0});
					else
						this.ReplaceRange("", from, sel ? to : {line: from.line});

				}, this),
				deleteLine: BX.proxy(function(){
					var l = this.GetCursor().line;
					this.ReplaceRange("", {line: l, ch: 0}, {line: l});
				}, this),
				goCharLeft:BX.proxy(function(){this.MoveH(-1, "char");}, this),
				goCharRight: BX.proxy(function(){this.MoveH(1, "char");}, this),
				goColumnLeft: BX.proxy(function(){this.MoveH(-1, "column");}, this),
				goColumnRight: BX.proxy(function(){this.MoveH(-1, "column");}, this),
				goDocStart: BX.proxy(function(){this.SetCursor(0, 0, true);}, this),
				goDocEnd: BX.proxy(function(){this.DoSelection({line: this.oDoc.size - 1}, null, true);}, this),
				goLineStart: BX.proxy(function(){this.SetCursor(this.GetCursor().line, 0, true);}, this),
				goHome: BX.proxy(function(){
					var
						cur = this.GetCursor(),
						text = this.GetLineText(cur.line),
						firstNonWS = Math.max(0, text.search(/\S/));
					this.SetCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
				}, this),
				goLineEnd: BX.proxy(function(){this.DoSelection({line: this.GetCursor().line}, null, true);}, this),
				goLineUp: BX.proxy(function(){this.MoveV(-1, "line");}, this),
				goLineDown: BX.proxy(function(){this.MoveV(1, "line");}, this),
				goPageUp: BX.proxy(function(){this.MoveV(-1, "page");}, this),
				goPageDown: BX.proxy(function(){this.MoveV(1, "page");}, this),
				goWordLeft: BX.proxy(function(){this.MoveH(-1, "word");}, this),
				goWordRight: BX.proxy(function(){this.MoveH(1, "word");}, this),
				delCharLeft: BX.proxy(function(){this.DeleteH(-1, "char");}, this),
				delCharRight: BX.proxy(function(){this.DeleteH(1, "char");}, this),
				delWordLeft: BX.proxy(function(){this.DeleteH(-1, "word");}, this),
				delWordRight: BX.proxy(function(){this.DeleteH(1, "word");}, this),
				indentLess: BX.proxy(function(){this.IndentSelection("subtract");}, this),
				tab: BX.proxy(function(){if (this.PosEq(this.oSel.from, this.oSel.to)){this.ReplaceSelection("\t", "end");}else{this.IndentSelection("add");}}, this),
				undo: BX.proxy(this.Undo, this),
				//redo: BX.proxy(this.Redo, this),
				enter: BX.proxy(function(){
					this.ReplaceSelection("\n", "end");
					this.IndentLine(this.GetCursor().line);
				}, this)
			}[command];
		},

		IndentSelection: function(syntax)
		{
			this.OnBeforeAction();
			this.IndentSelected(syntax);
			this.OnAfterAction();
		},

		DoSelection: function(from, to, bUser)
		{
			this.OnBeforeAction();
			var
				f = this.ClipPos(from),
				t = this.ClipPos(to || from);

			if (bUser)
				this.SetSelectionUser(f, t);
			else
				this.SetSelection(f, t);

			this.OnAfterAction();
		},

		GetLineText: function(line)
		{
			if (line >= 0 && line < this.oDoc.size)
				return this.GetLine(line).text;
			return '';
		},


		CheckLinesHeight: function(textValue, lineStart, lineEnd)
		{
			var arStr = textValue.split("\n"), i, s, h, result = false, h0;
			//if(lineEnd === false)
			lineEnd = arStr.length - 1;
			//if(lineStart < 0)
			lineStart = 0;

			for(i = lineStart; i <= lineEnd; i++)
			{
				if(s = this.pLineNumTA.childNodes[i])
				{
					h = this.GetLineHeight(arStr[i]);
					h0 = parseInt(s.style.height);
					if (h > this.lineHeight || s.style.height != "")
					{
						s.style.height = arStr[i] ? h + "px" : "";
						if (h0 != h) // Now we know that at least one line was changed
							result = true;
					}
				}
			}

			return result;
		},

		// Add new line nums or hide if count more than needed
		BuildLineNum: function(count)
		{
			count = parseInt(count);
			if (count !== this.lineNums)
			{
				if (count < 1)
					count = 1;

				if (this.lineNums < count)
				{
					for (var i = this.lineNums + 1; i <= count; i++)
						this.pLineNumTA.appendChild(BX.create("DIV", {html: i}));

					this.lineNums = count;
				}
			}
		},

		TextOnKeydown: function(e)
		{
			if(!e)
				e = window.event;

			var key = e.which || e.keyCode;
			if(key == 9) // Tab
			{
				this.OnTab(e, this.ShiftPressed(e));
				return BX.PreventDefault(e);
			}
		},

		TextOnKeyup: function(e, key)
		{
			if(!e)
				e = window.event;

			if (!key)
				key = e.which || e.keyCode;

			//17 - Ctrl, 18 Alt, 9 - tab, 27 - esc
			if (!{17: true, 18 : true, 9: true, 27: true}[key])
				this.CheckLineSelection();
		},

		TextOnMousedown: function(e)
		{
			this.MoveLineSelection(true);
		},

		ShiftPressed: function(e)
		{
			if (window.event)
				return !!window.event.shiftKey;
			return !!(e.shiftKey || e.modifiers > 3);
		},

		OnTab: function(e, bShift)
		{
			if(this.bDisableTab)
				return;

			if (BX.browser.IsIE())
				this.bDisableTab = true;

			var
				endText, startText, tmp,
				tab = "\t",
				taSel = this.GetTASelection(),
				from = taSel.start,
				to = taSel.end,
				source = this.GetTAValue().replace(/\r/g, ""),
				txt = source.substring(from, to),
				posFrom = from,
				posTo = to;

			if (!bShift) // Insert TABulation
			{
				if (txt == "") // One line
				{
					source = source.substr(0, from) + tab + source.substr(to);
					posFrom = from + 1;
					posTo = posFrom;
				}
				else
				{
					from = source.substr(0, from).lastIndexOf("\n") + 1;
					if (from <= 0)
						from = 0;
					endText = source.substr(to);
					startText = source.substr(0, from);
					tmp = source.substring(from, to).split("\n");
					txt = tab + tmp.join("\n" + tab);
					source = startText + txt + endText;
					posFrom = from;
					posTo= source.indexOf("\n", startText.length + txt.length);

					if(posTo == -1)
						posTo = source.length;
				}
			}
			else // Remove TABulation
			{
				if (txt == "") // One line
				{
					if (from <= 0)
						from = 1;
					if(source.substring(from - 1, from) == tab)
					{
						source = source.substr(0, from - 1) + source.substr(to);
						posFrom = posTo = from - 1;
					}
				}
				else
				{
					from = source.substr(0, from).lastIndexOf("\n") + 1;
					endText = source.substr(to);
					startText = source.substr(0, from);
					tmp = source.substring(from, to).split("\n");
					txt = "";
					for(i = 0; i < tmp.length; i++)
					{
						for(j = 0, l = this.tabSymTA.length; j < l; j++)
						{
							if(tmp[i].charAt(0) == tab)
							{
								tmp[i] = tmp[i].substr(1);
								j = l;
							}
						}
						txt += tmp[i];
						if(i < tmp.length - 1)
							txt += "\n";
					}

					source = startText + txt + endText;
					posFrom = from;
					posTo = source.indexOf("\n", startText.length + txt.length);
					if(posTo == -1)
						posTo = source.length;
				}
			}

			this.SetTAValue(source);

			if (BX.browser.IsIE())
			{
				var _this = this;
				this.SetIETASelection(posFrom, posTo);
				setTimeout(function(){_this.bDisableTab = false;}, 80);
			}
			else
			{
				this.pTA.selectionStart = posFrom;
				this.pTA.selectionEnd = posTo;
			}

			this.CheckLineSelection(true);
		},

		CheckLineSelection: function(bChanges)
		{
			if (this.highlightMode)
				return;

			var sel = this.GetSelectionInfo();

			if (bChanges !== false || sel.source != this.curSel.source)
				this.ManageSize(false);

			this.curSel = sel;
		},

		ManageSize: function(bCheckLines)
		{
			if(this.highlightMode)
				return false;

			var taW, taH;
			//Handle height ! it's important to handle height first
			taH = parseInt(this.pTA.scrollHeight, 10);

			this.curSel = this.GetSelectionInfo();
			if(!isNaN(taH) && taH >= 0)
			{
				this.pContTA.style.height = (taH + 2) + "px";
				this.pTA.style.height = taH + "px";
			}

			taW = this.pTA.scrollWidth;
			if (parseInt(this.pTA.style.width) < taW)
			{
				this.pTA.style.width = taW + "px";
				this.pContTA.style.width = taW + "px";
			}

			if (bCheckLines !== false)
				this.CheckLineSelection(true);

			if(this.curSel.nbLine >= 0)
			{
				this.BuildLineNum(this.curSel.nbLine);

				var lastLine = this.GetLinePos(this.curSel.nbLine); // Last line position
				if (lastLine.top + lastLine.height < taH && this.curSel.nbLine < this.lineNums)
				{
					var h = parseInt(lastLine.top + lastLine.height);
					if (!isNaN(h) && h >= 0)
					{
						this.pContTA.style.height = (h + 2) + "px";
						this.pTA.style.height = h + "px";
					}
				}
			}

			this.pTA.scrollTop = "0px";
			this.pTA.scrollLeft = "0px";
		},

		GetSelectionInfo: function()
		{
			if (!this.curSel)
				this.curSel = {};

			var
				taSel = this.GetTASelection(),
				start = taSel.start,
				end = taSel.end,
				str;

			var
				sel = {
					selectionStart: start,
					selectionEnd: end,
					source: this.GetTAValue().replace(/\r/g, ""),
					linePos: 1,
					lineNb: 1,
					carretPos: 0,
					curLine: "",
					cursorIndex: 0,
					direction: this.curSel.direction
				},
				splitTab = sel.source.split("\n"),
				nbLine = splitTab.length,
				nbChar = sel.source.length - (nbLine - 1);

			if (nbChar < 0)
				nbChar = 0;

			sel.nbLine = nbLine;
			sel.nbChar = nbChar;

			if(start > 0)
			{
				str = sel.source.substr(0,start);
				sel.carretPos = start - str.lastIndexOf("\n");
				sel.linePos = str.split("\n").length;
			}
			else
			{
				sel.carretPos = 1;
			}

			if (sel.linePos < 1)
				sel.linePos = 1;

			if(end > start)
				sel.lineNb = sel.source.substring(start, end).split("\n").length;

			sel.cursorIndex = start;
			sel.curLine = splitTab[sel.linePos - 1];

			// Check selection direction
			if(sel.selectionStart == this.curSel.selectionStart)
			{
				if(sel.selectionEnd > this.curSel.selectionEnd)
					sel.direction = "down";
				else //if(sel.selectionEnd == this.curSel.selectionStart)
					sel.direction = this.curSel.direction;
			}
			else if(sel.selectionStart == this.curSel.selectionEnd && sel.selectionEnd > this.curSel.selectionEnd)
			{
				sel.direction = "down";
			}
			else
			{
				sel.direction = "up";
			}

			this.SetStatusBarInfo(sel);
			return sel;
		},

		GetTASelection: function()
		{
			var start = 0, end = 0;
			try
			{
				if (BX.browser.IsIE9() || this.pTA.selectionStart != undefined)
				{
					start = this.pTA.selectionStart;
					end = this.pTA.selectionEnd;
				}
				else if (BX.browser.IsIE())
				{
					var ch = "\001";
					var range = document.selection.createRange();
					var savedText = range.text.replace(/\r/g, "");
					var dubRange = range.duplicate();
					if (savedText != '')
						range.collapse();
					dubRange.moveToElementText(this.pTA);
					range.text = ch;
					end = start = dubRange.text.replace(/\r/g, "").indexOf(ch);
					range.moveStart('character', -1);
					range.text = "";

					if (savedText != '')
						end += savedText.length;
				}
				else
				{
					start = this.pTA.selectionStart;
					end = this.pTA.selectionEnd;
				}
			}
			catch(e)
			{
				start = 0;
				end = 0;
			}

			return {start: start, end: end};
		},

		GetLinePos: function(ind)
		{
			var
				pLine = this.pLineNumTA.childNodes[ind - 1],
				top = pLine ? pLine.offsetTop : this.lineHeight * (ind - 1),
				height = pLine ? pLine.offsetHeight : this.lineHeight;

			return {
				top: parseInt(top) || 0,
				height: parseInt(height) || 0
			};
		},

		Undo: function()
		{
			this.OnBeforeAction();
			this.UndoRedo(this.oHistory.done, this.oHistory.undone);
			this.OnAfterAction();
		},

//		Redo: function()
//		{
//			this.OnBeforeAction();
//			this.UndoRedo(this.oHistory.undone, this.oHistory.done);
//			this.OnAfterAction();
//		},

		UndoRedo: function(from, to)
		{
			if (!from.length)
				return;
			var
				i,change,replaced,end,pos,
				set = from.pop(),
				out = [];

			for (i = set.length - 1; i >= 0; i -= 1)
			{
				change = set[i];
				replaced = [];
				end = change.start + change.added;
				pos = {
					line: change.start + change.old.length - 1,
					ch: this.GetUndoChar(replaced[replaced.length - 1], change.old[change.old.length - 1])
				};
				this.UpdateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: this.GetLine(end - 1).text.length}, change.old, pos, pos);
			}
			this.updateInput = true;
			to.push(out);
		},

		GetUndoChar: function(from, to)
		{
			if (!to)
				return 0;
			if (!from)
				return to.length;

			for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
				if (from.charAt(i) != to.charAt(j))
					break;

			return j + 1;
		}
	};

	// The character stream used by a syntax's parser.
	function JCLineChain(string, tabSize)
	{
		this.pos = this.start = 0;
		this.string = string;
		this.tabSize = tabSize || 8;
	}

	JCLineChain.prototype = {
		eol: function ()
		{
			return this.pos >= this.string.length;
		},
		sol: function ()
		{
			return this.pos == 0;
		},
		peek: function ()
		{
			return this.string.charAt(this.pos) || undefined;
		},
		next: function ()
		{
			if (this.pos < this.string.length)
				return this.string.charAt(this.pos++);
		},
		eat: function (match)
		{
			var
				ch = this.string.charAt(this.pos),
				ok = (typeof match == "string") ? (ch == match) : (ch && (match.test ? match.test(ch) : match(ch)));

			if (ok)
			{
				++this.pos;
				return ch;
			}
		},
		eatWhile: function (match)
		{
			var start = this.pos;
			while (this.eat(match))
			{
			}
			return this.pos > start;
		},
		eatSpace: function ()
		{
			var start = this.pos;
			while (/[\s\u00a0]/.test(this.string.charAt(this.pos)))
				++this.pos;
			return this.pos > start;
		},
		skipToEnd: function ()
		{
			this.pos = this.string.length;
		},
		skipTo: function (ch)
		{
			var found = this.string.indexOf(ch, this.pos);
			if (found > -1)
			{
				this.pos = found;
				return true;
			}
		},
		backUp: function (n)
		{
			this.pos -= n;
		},
		column: function ()
		{
			return countColumn(this.string, this.start, this.tabSize);
		},
		indentation: function ()
		{
			return countColumn(this.string, null, this.tabSize);
		},
		match: function (pattern, consume, caseInsensitive)
		{
			if (typeof pattern == "string")
			{
				var cased = function (str)
				{
					return caseInsensitive ? str.toLowerCase() : str;
				};
				if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos)
				{
					if (consume !== false)
						this.pos += pattern.length;
					return true;
				}
			}
			else
			{
				var match = this.string.slice(this.pos).match(pattern);
				if (match && consume !== false)
					this.pos += match[0].length;
				return match;
			}
		},
		current: function ()
		{
			return this.string.slice(this.start, this.pos);
		}
	};

	function JCLine(text, styles)
	{
		this.styles = styles || [text, null];
		this.text = text;
		this.height = 1;
	}

	JCLine.inheritMarks = function (text, orig)
	{
		var
			ln = new JCLine(text),
			mk = orig && orig.marked;

		if (mk)
		{
			for (var i = 0; i < mk.length; ++i)
			{
				if (mk[i].to == null && mk[i].style)
				{
					var
						newmk = ln.marked || (ln.marked = []),
						mark = mk[i],
						nmark = mark.dup();

					newmk.push(nmark);
					nmark.attach(ln);
				}
			}
		}
		return ln;
	};

	JCLine.prototype = {
		Replace: function (from, to_, text)
		{
			var
				st = [],
				mk = this.marked,
				to = to_ == null ? this.text.length : to_;

			copyStyles(0, from, this.styles, st);
			if (text) st.push(text, null);
			copyStyles(to, this.text.length, this.styles, st);
			this.styles = st;
			this.text = this.text.slice(0, from) + text + this.text.slice(to);
			this.statusAfter = null;
			if (mk)
			{
				var diff = text.length - (to - from);
				for (var i = 0; i < mk.length; ++i)
				{
					var mark = mk[i];
					mark.clipTo(from == null, from || 0, to_ == null, to, diff);
					if (mark.isDead())
					{
						mark.detach(this);
						mk.splice(i--, 1);
					}
				}
			}
		},
		split: function (pos, textBefore)
		{
			var
				st = [textBefore, null],
				mk = this.marked;

			copyStyles(pos, this.text.length, this.styles, st);
			var taken = new JCLine(textBefore + this.text.slice(pos), st);
			if (mk)
			{
				for (var i = 0; i < mk.length; ++i)
				{
					var mark = mk[i];
					var newmark = mark.split(pos, textBefore.length);
					if (newmark)
					{
						if (!taken.marked)
							taken.marked = [];
						taken.marked.push(newmark);
						newmark.attach(taken);
						if (newmark == mark)
							mk.splice(i--, 1);
					}
				}
			}
			return taken;
		},

		append: function (line)
		{
			var mylen = this.text.length, mk = line.marked, mymk = this.marked;
			this.text += line.text;
			copyStyles(0, line.text.length, line.styles, this.styles);
			if (mymk)
			{
				for (var i = 0; i < mymk.length; ++i)
					if (mymk[i].to == null) mymk[i].to = mylen;
			}
			if (mk && mk.length)
			{
				if (!mymk) this.marked = mymk = [];
				outer: for (var i = 0; i < mk.length; ++i)
				{
					var mark = mk[i];
					if (!mark.from)
					{
						for (var j = 0; j < mymk.length; ++j)
						{
							var mymark = mymk[j];
							if (mymark.to == mylen && mymark.sameSet(mark))
							{
								mymark.to = mark.to == null ? null : mark.to + mylen;
								if (mymark.isDead())
								{
									mymark.detach(this);
									mk.splice(i--, 1);
								}
								continue outer;
							}
						}
					}
					mymk.push(mark);
					mark.attach(this);
					mark.from += mylen;
					if (mark.to != null) mark.to += mylen;
				}
			}
		},
		fixMarkEnds: function (other)
		{
			var mk = this.marked, omk = other.marked;
			if (!mk) return;
			outer: for (var i = 0; i < mk.length; ++i)
			{
				var mark = mk[i], close = mark.to == null;
				if (close && omk)
				{
					for (var j = 0; j < omk.length; ++j)
					{
						var om = omk[j];
						if (!om.sameSet(mark) || om.from != null) continue;
						if (mark.from == this.text.length && om.to == 0)
						{
							omk.splice(j, 1);
							mk.splice(i--, 1);
							continue outer;
						}
						else
						{
							close = false;
							break;
						}
					}
				}
				if (close) mark.to = this.text.length;
			}
		},
		fixMarkStarts: function ()
		{
			var mk = this.marked;
			if (!mk) return;
			for (var i = 0; i < mk.length; ++i)
				if (mk[i].from == null) mk[i].from = 0;
		},
		addMark: function (mark)
		{
			mark.attach(this);
			if (this.marked == null)
				this.marked = [];
			this.marked.push(mark);
			this.marked.sort(function (a, b)
			{
				return (a.from || 0) - (b.from || 0);
			});
		},

		highlight: function (syntax, status, tabSize)
		{
			var stream = new JCLineChain(this.text, tabSize), st = this.styles, pos = 0;
			var changed = false, curWord = st[0], prevWord;
			if (this.text == "" && syntax.blankLine) syntax.blankLine(status);
			while (!stream.eol())
			{
				var style = syntax.HandleChar(stream, status);
				var substr = this.text.slice(stream.start, stream.pos);
				stream.start = stream.pos;
				if (pos && st[pos - 1] == style)
				{
					st[pos - 2] += substr;
				}
				else if (substr)
				{
					if (!changed && (st[pos + 1] != style || (pos && st[pos - 2] != prevWord))) changed = true;
					st[pos++] = substr;
					st[pos++] = style;
					prevWord = curWord;
					curWord = st[pos];
				}
				// Give up when line is ridiculously long
				if (stream.pos > 5000)
				{
					st[pos++] = this.text.slice(stream.pos);
					st[pos++] = null;
					break;
				}
			}
			if (st.length != pos)
			{
				st.length = pos;
				changed = true;
			}
			if (pos && st[pos - 2] != prevWord)
				changed = true;

			return changed || (st.length < 5 && this.text.length < 10 ? null : false);
		},
		getTokenAt: function (syntax, status, tabSize, ch)
		{
			var
				style,
				txt = this.text,
				stream = new JCLineChain(txt, tabSize);
			while (stream.pos < ch && !stream.eol())
			{
				stream.start = stream.pos;
				style = syntax.HandleChar(stream, status);
			}

			return {
				start: stream.start,
				end: stream.pos,
				string: stream.current(),
				className: style || null,
				status: status
			};
		},
		indentation: function (tabSize)
		{
			return countColumn(this.text, null, tabSize);
		},
		getElement: function (makeTab, wrapAt, wrapWBR)
		{
			var _this = this;
			var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
			var pre = BX.create("PRE");

			function span_(html, text, style)
			{
				if (!text)
					return;
				// Work around a bug where, in some compat syntaxs, IE ignores leading spaces
				if (first && BX.browser.IsIE() && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
				first = false;
				if (!specials.test(text))
				{
					col += text.length;
					var content = document.createTextNode(text);
				}
				else
				{
					var content = document.createDocumentFragment(), pos = 0;
					while (true)
					{
						specials.lastIndex = pos;
						var m = specials.exec(text);
						var skipped = m ? m.index - pos : text.length - pos;
						if (skipped)
						{
							content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
							col += skipped;
						}
						if (!m) break;
						pos += skipped + 1;
						if (m[0] == "\t")
						{
							var tab = makeTab(col);
							content.appendChild(tab.element.cloneNode(true));
							col += tab.width;
						}
						else
						{
							content.appendChild(BX.create("TT", {props: {className: "bxce-invalidchar", title: "\\u" + m[0].charCodeAt(0).toString(16)}, html: "\u2022"}));
							col += 1;
						}
					}
				}
				if (style)
					html.appendChild(BX.create("TT", {props: {className: style}})).appendChild(content);
				else
					html.appendChild(content);
			}

			var span = span_;
			if (wrapAt != null)
			{
				var
					outPos = 0,
					anchor = pre.anchor = BX.create("SPAN");
				span = function (html, text, style)
				{
					var l = text.length;
					if (wrapAt >= outPos && wrapAt < outPos + l)
					{
						if (wrapAt > outPos)
						{
							span_(html, text.slice(0, wrapAt - outPos), style);
							// See comment at the definition of spanAffectsWrapping
							if (wrapWBR)
								html.appendChild(BX.create("WBR"));
						}
						html.appendChild(anchor);
						var cut = wrapAt - outPos;
						span_(anchor, BX.browser.IsOpera() ? text.slice(cut, cut + 1) : text.slice(cut), style);
						if (BX.browser.IsOpera())
							span_(html, text.slice(cut + 1), style);
						wrapAt--;
						outPos += l;
					}
					else
					{
						outPos += l;
						span_(html, text, style);
						if (outPos == wrapAt && outPos == len)
						{
							BX.adjust(anchor, {text: _this.GetEolHtml()});
							html.appendChild(anchor);
						}
						// Stop outputting HTML when gone sufficiently far beyond measure
						else if (outPos > wrapAt + 10 && /\s/.test(text))
						{
							span = function ()
							{
							};
						}
					}
				};
			}

			var st = this.styles, allText = this.text, marked = this.marked;
			var len = allText.length;

			function styleToClass(style)
			{
				if (!style)
					return null;
				return "bxce-" + style.replace(/ +/g, " bxce-");
			}

			if (!allText && wrapAt == null)
			{
				span(pre, " ");
			}
			else if (!marked || !marked.length)
			{
				for (var i = 0, ch = 0; ch < len; i += 2)
				{
					var str = st[i], style = st[i + 1], l = str.length;
					if (ch + l > len) str = str.slice(0, len - ch);
					ch += l;
					span(pre, str, styleToClass(style));
				}
			}
			else
			{
				var pos = 0, i = 0, text = "", style, sg = 0;
				var nextChange = marked[0].from || 0, marks = [], markpos = 0;
				var advanceMarks = function ()
				{
					var m;
					while (markpos < marked.length &&
						((m = marked[markpos]).from == pos || m.from == null))
					{
						if (m.style != null) marks.push(m);
						++markpos;
					}
					nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
					for (var i = 0; i < marks.length; ++i)
					{
						var to = marks[i].to;
						if (to == null)
							to = Infinity;
						if (to == pos)
							marks.splice(i--, 1);
						else
							nextChange = Math.min(to, nextChange);
					}
				};
				var m = 0;
				while (pos < len)
				{
					if (nextChange == pos) advanceMarks();
					var upto = Math.min(len, nextChange);
					while (true)
					{
						if (text)
						{
							var end = pos + text.length;
							var appliedStyle = style;
							for (var j = 0; j < marks.length; ++j)
								appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
							span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
							if (end >= upto)
							{
								text = text.slice(upto - pos);
								pos = upto;
								break;
							}
							pos = end;
						}
						text = st[i++];
						style = styleToClass(st[i++]);
					}
				}
			}
			return pre;
		},
		cleanUp: function ()
		{
			this.parent = null;
			if (this.marked)
			{
				for (var i = 0, e = this.marked.length; i < e; ++i)
					this.marked[i].detach(this);
			}
		},

		GetEolHtml: function()
		{
			if (this._eolHtml != undefined)
				return this._eolHtml;

			this._eolHtml = " ";
			if (BX.browser.IsFirefox() || BX.browser.IsIE())
				this._eolHtml = "\u200b";
			else if (BX.browser.IsOpera())
				this._eolHtml = "";

			return this._eolHtml;
		}
	};

	// Utility used by replace and split above
	function copyStyles(from, to, source, dest)
	{
		for (var i = 0, pos = 0, status = 0; pos < to; i += 2)
		{
			var
				part = source[i],
				end = pos + part.length;

			if (status == 0)
			{
				if (end > from)
					dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i + 1]);
				if (end >= from)
					status = 1;
			}
			else if (status == 1)
			{
				if (end > to)
					dest.push(part.slice(0, to - pos), source[i + 1]);
				else
					dest.push(part, source[i + 1]);
			}
			pos = end;
		}
	}

	// Data structure contains lines.
	function JCLineHolder(lines)
	{
		this.lines = lines;
		this.parent = null;
		for (var i = 0, e = lines.length, height = 0; i < e; ++i)
		{
			lines[i].parent = this;
			height += lines[i].height;
		}
		this.height = height;
	}

	JCLineHolder.prototype = {
		GetSize: function ()
		{
			return this.lines.length;
		},
		Remove: function (at, n, callbacks)
		{
			for (var i = at, e = at + n; i < e; ++i)
			{
				var line = this.lines[i];
				this.height -= line.height;
				line.cleanUp();
				if (line.handlers)
				{
					for (var j = 0; j < line.handlers.length; ++j)
						callbacks.push(line.handlers[j]);
				}
			}
			this.lines.splice(at, n);
		},
		Collapse: function (lines)
		{
			lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
		},
		SetHeight: function (at, lines, height)
		{
			this.height += height;
			this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
			for (var i = 0, e = lines.length; i < e; ++i)
				lines[i].parent = this;
		},
		_Iteration: function (at, n, op)
		{
			for (var e = at + n; at < e; ++at)
				if (op(this.lines[at]))
					return true;
		}
	};

	function JCDocHolder(children)
	{
		this.children = children;
		var
			i, size = 0,
			height = 0;
		for (i = 0; i < children.length; ++i)
		{
			size += children[i].GetSize();
			height += children[i].height;
			children[i].parent = this;
		}
		this.size = size;
		this.height = height;
		this.parent = null;
	}

	JCDocHolder.prototype = {
		GetSize: function ()
		{
			return this.size;
		},
		Remove: function (at, n, callbacks)
		{
			this.size -= n;
			var i, child,sz, rm;
			for (i = 0; i < this.children.length; ++i)
			{
				child = this.children[i],
					sz = child.GetSize();
				if (at < sz)
				{
					rm = Math.min(n, sz - at), oldHeight = child.height;
					child.Remove(at, rm, callbacks);
					this.height -= oldHeight - child.height;
					if (sz == rm)
					{
						this.children.splice(i--, 1);
						child.parent = null;
					}
					if ((n -= rm) == 0)
						break;
					at = 0;
				}
				else
				{
					at -= sz;
				}
			}
			if (this.size - n < 25)
			{
				var lines = [];
				this.Collapse(lines);
				this.children = [new JCLineHolder(lines)];
				this.children[0].parent = this;
			}
		},
		Collapse: function (lines)
		{
			for (var i = 0, e = this.children.length; i < e; ++i)
				this.children[i].Collapse(lines);
		},
		Insert: function (at, lines)
		{
			var height = 0;
			for (var i = 0, e = lines.length; i < e; ++i)
				height += lines[i].height;
			this.SetHeight(at, lines, height);
		},
		SetHeight: function (at, lines, height)
		{
			this.size += lines.length;
			this.height += height;

			var i, child, size, spilled, lineHolder;
			for (i = 0; i < this.children.length; ++i)
			{
				child = this.children[i];
				size = child.GetSize();
				if (at <= size)
				{
					child.SetHeight(at, lines, height);
					if (child.lines && child.lines.length > 50)
					{
						while (child.lines.length > 50)
						{
							spilled = child.lines.splice(child.lines.length - 25, 25);
							lineHolder = new JCLineHolder(spilled);
							child.height -= lineHolder.height;
							this.children.splice(i + 1, 0, lineHolder);
							lineHolder.parent = this;
						}
						this.CheckSpill();
					}
					break;
				}
				at -= size;
			}
		},
		CheckSpill: function ()
		{
			if (this.children.length <= 10)
				return;
			var
				spilled,sibling, copy,
				_this = this;
			do {
				spilled = _this.children.splice(_this.children.length - 5, 5);
				sibling = new JCDocHolder(spilled);
				if (!_this.parent)
				{
					copy = new JCDocHolder(_this.children);
					copy.parent = _this;
					_this.children = [copy, sibling];
					_this = copy;
				}
				else
				{
					_this.size -= sibling.size;
					_this.height -= sibling.height;
					_this.parent.children.splice(indexOf(_this.parent.children, _this) + 1, 0, sibling);
				}
				sibling.parent = _this.parent;
			}

			while (_this.children.length > 10);
			_this.parent.CheckSpill();
		},

		Iteration: function (from, to, op)
		{
			this._Iteration(from, to - from, op);
		},

		_Iteration: function (at, n, op)
		{
			var i, used,size;
			for (i = 0; i < this.children.length; ++i)
			{
				size = this.children[i].GetSize();
				if (at < size)
				{
					used = Math.min(n, size - at);
					if (this.children[i]._Iteration(at, used, op))
						return true;
					if ((n -= used) == 0)
						break;
					at = 0;
				}
				else
				{
					at -= size;
				}
			}
		}
	};

	function lineNo(line)
	{
		if (line.parent == null) return null;
		var cur = line.parent, no = indexOf(cur.lines, line);
		for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent)
		{
			for (var i = 0, e = chunk.children.length; ; ++i)
			{
				if (chunk.children[i] == cur) break;
				no += chunk.children[i].GetSize();
			}
		}
		return no;
	}

	function History()
	{
		this.time = 0;
		this.done = [];
		this.undone = [];
		this.compound = 0;
		this.closed = false;
	}

	History.prototype = {
		addChange: function (start, added, old)
		{
			this.undone.length = 0;
			var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
			var dtime = time - this.time;

			if (this.compound && cur && !this.closed)
			{
				cur.push({start: start, added: added, old: old});
			}
			else if (dtime > 400 || !last || this.closed ||
				last.start > start + old.length || last.start + last.added < start)
			{
				this.done.push([
					{start: start, added: added, old: old}
				]);
				this.closed = false;
			}
			else
			{
				var startBefore = Math.max(0, last.start - start),
					endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
				for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
				for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
				if (startBefore) last.start = start;
				last.added += added - (old.length - startBefore - endAfter);
			}
			this.time = time;
		},
		startCompound: function ()
		{
			if (!this.compound++) this.closed = true;
		},
		endCompound: function ()
		{
			if (!--this.compound) this.closed = true;
		}
	};

//	function e_preventDefault(e)
//	{
//		if (e.preventDefault)
//			e.preventDefault();
//		else
//			e.returnValue = false;
//	}
//	function stopPropagation(e)
//	{
//		if (e.stopPropagation)
//			e.stopPropagation();
//		else
//			e.cancelBubble = true;
//	}

//	function e_stop(e)
//	{
//		e_preventDefault(e);
//		e_stopPropagation(e);
//	}

	//JCCodeEditor.e_stop = e_stop;
	//JCCodeEditor.e_preventDefault = e_preventDefault;
	//JCCodeEditor.e_stopPropagation = e_stopPropagation;

	function mouseButton(e)
	{
		var but = e.which;
		if (but == null)
		{
			if (e.button & 1)
				but = 1;
			else if (e.button & 2)
				but = 3;
			else if (e.button & 4)
				but = 2;
		}
		if (BX.browser.IsMac() && e.ctrlKey && but == 1)
			but = 3;
		return but;
	}

	function Delayed()
	{
		this.id = null;
	}

	Delayed.prototype = {
		set: function (ms, f)
		{
			clearTimeout(this.id);
			this.id = setTimeout(f, ms);
		}
	};


// TODO: !!!!!!!!!!!!!!!!!!!!!!!
//	var Pass = JCCodeEditor.Pass = {toString: function ()
//	{
//		return "JCCodeEditor.Pass";
//	}};
//
//	var lineSep = function ()
//	{
//		var te = BX.create("TEXTAREA");
//		te.value = "foo\nbar";
//		if (te.value.indexOf("\r") > -1)
//			return "\r\n";
//		return "\n";
//	}();

	// Counts the column offset in a string, taking tabs into account.
	// Used mostly to find indentation.
	function countColumn(string, end, tabSize)
	{
		tabSize = 4;
		if (end == null)
		{
			end = string.search(/[^\s\u00a0]/);
			if (end == -1)
				end = string.length;
		}

		for (var i = 0, n = 0; i < end; ++i)
		{
			if (string.charAt(i) == "\t")
				n += tabSize - (n % tabSize);
			else
				++n;
		}
		return n;
	}

	function GetOffset(node, screen)
	{
		try
		{
			var box = node.getBoundingClientRect();
			box = {
				top: box.top,
				left: box.left
			};
		}
		catch (e)
		{
			box = {
				top: 0,
				left: 0
			};
		}

		if (!screen)
		{
			// Get the toplevel scroll, working around browser differences.
			if (window.pageYOffset == null)
			{
				var
					doc = document.documentElement || document.body.parentNode;
				if (doc.scrollTop == null)
					doc = document.body;
				box.top += doc.scrollTop;
				box.left += doc.scrollLeft;
			}
			else
			{
				box.top += window.pageYOffset;
				box.left += window.pageXOffset;
			}
		}
		return box;
	}

	function indexOf(haystack, needle)
	{
		if (haystack.indexOf)
			return haystack.indexOf(needle);

		if (haystack.length)
		{
			for (var i = 0; i < haystack.length; ++i)
				if (haystack[i] == needle)
					return i;
		}

		return -1;
	}

	function isWordChar(ch)
	{
		return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
	}

	// ************* Syntaxes defenitions *************
	{
		var Syntaxes = {};

		function copySyntaxStatus(syntax, status)
		{
			if (status === true)
				return status;

			if (syntax.copyStatus)
				return syntax.copyStatus(status);

			var newStatus = {}, n;
			for (n in status)
			{
				var val = status[n];
				if (val instanceof Array)
					val = val.concat([]);
				newStatus[n] = val;
			}
			return newStatus;
		}

		function prepareKeywords(arr)
		{
			var keys = {}, i;
			for (i = 0; i < arr.length; ++i)
				keys[arr[i]] = true;
			return keys;
		}

		Syntaxes.html = function()
		{
			var
				indentUnit = 4,
				arTags = {
					autoSelfClosers: prepareKeywords(['area', 'base', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']),
					implicitlyClosed: prepareKeywords(['dd', 'li', 'optgroup', 'option', 'p', 'rp', 'rt', 'tbody', 'td', 'tfoot', 'th', 'tr']),
					contextGrabbers: {
						dd: prepareKeywords(['dd', 'dt']),
						dt: prepareKeywords(['dd', 'dt']),
						li: prepareKeywords(['li']),
						option: prepareKeywords(['option', 'optgroup']),
						optgroup: prepareKeywords(['optgroup']),
						p: prepareKeywords(['address', 'article', 'aside', 'blockquote', 'dir', 'div', 'dl', 'fieldset', 'footer', 'form','h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'menu', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul']),
						rp: prepareKeywords(['rp', 'rt']),
						rt: prepareKeywords(['rp', 'rt']),
						tbody: prepareKeywords(['tbody', 'tfoot']),
						td: prepareKeywords(['td', 'th']),
						tfoot: prepareKeywords(['tbody']),
						th: prepareKeywords(['td', 'th']),
						thead: prepareKeywords(['tbody', 'tfoot']),
						tr: prepareKeywords(['tr'])
					},
					doNotIndent: prepareKeywords(['pre']),
					allowUnquoted: true,
					allowMissing: true
				},
				tagName, type;

			function InsideText(stream, status)
			{
				function chain(parser)
				{
					status.tokenize = parser;
					return parser(stream, status);
				}

				var ch = stream.next(), c, bAtom;
				if (ch == "<")
				{
					if (stream.eat("!"))
					{
						if (stream.eat("["))
						{
							return stream.match("CDATA[") ? chain(InsideBlock("atom", "]]>")) : null;
						}
						else if (stream.match("--"))
						{
							return chain(InsideBlock("comment", "-->"));
						}
						else if (stream.match("DOCTYPE", true, true))
						{
							stream.eatWhile(/[\w\._\-]/);
							return chain(Doctype(1));
						}
						else
						{
							return null;
						}
					}
					else if (stream.eat("?"))
					{
						stream.eatWhile(/[\w\._\-]/);
						status.tokenize = InsideBlock("meta", "?>");
						return "meta";
					}
					else
					{
						type = stream.eat("/") ? "closeTag" : "openTag";
						stream.eatSpace();
						tagName = "";
						while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/)))
							tagName += c;
						status.tokenize = InsideTag;
						return "tag";
					}
				}
				else if (ch == "&")
				{
					var bAtom;
					if (stream.eat("#"))
						bAtom = stream.eat("x") ? (stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";")) : (stream.eatWhile(/[\d]/) && stream.eat(";"));
					else
						bAtom = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");

					return bAtom ? "atom" : "error";
				}
				else
				{
					stream.eatWhile(/[^&<]/);
					return null;
				}
			}

			function InsideTag(stream, status)
			{
				var ch = stream.next();
				if (ch == ">" || (ch == "/" && stream.eat(">")))
				{
					status.tokenize = InsideText;
					type = ch == ">" ? "endTag" : "selfcloseTag";
					return "tag";
				}
				else if (ch == "=")
				{
					type = "equals";
					return null;
				}
				else if (/[\'\"]/.test(ch))
				{
					status.tokenize = InsideAttr(ch);
					return status.tokenize(stream, status);
				}
				else
				{
					stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
					return "word";
				}
			}

			function InsideAttr(quote)
			{
				return function(stream, status)
				{
					while (!stream.eol())
					{
						if (stream.next() == quote)
						{
							status.tokenize = InsideTag;
							break;
						}
					}
					return "string";
				};
			}

			function InsideBlock(style, terminator)
			{
				return function(stream, status) {
					while (!stream.eol()) {
						if (stream.match(terminator)) {
							status.tokenize = InsideText;
							break;
						}
						stream.next();
					}
					return style;
				};
			}

			function Doctype(depth)
			{
				return function(stream, status)
				{
					var ch;
					while ((ch = stream.next()) != null)
					{
						if (ch == "<")
						{
							status.tokenize = Doctype(depth + 1);
							return status.tokenize(stream, status);
						} else if (ch == ">")
						{
							if (depth == 1)
							{
								status.tokenize = InsideText;
								break;
							} else
							{
								status.tokenize = Doctype(depth - 1);
								return status.tokenize(stream, status);
							}
						}
					}
					return "meta";
				};
			}

			var curState, setStyle;
			function pass()
			{
				for (var i = arguments.length - 1; i >= 0; i--)
					curState.cc.push(arguments[i]);
			}
			function cont()
			{
				pass.apply(null, arguments);
				return true;
			}

			function pushContext(tagName, startOfLine)
			{
				var noIndent = arTags.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
				curState.context =
				{
					prev: curState.context,
					tagName: tagName,
					indent: curState.indented,
					startOfLine: startOfLine,
					noIndent: noIndent
				};
			}

			function popContext()
			{
				if (curState.context)
					curState.context = curState.context.prev;
			}

			function element(type)
			{
				if (type == "openTag")
				{
					curState.tagName = tagName;
					return cont(attributes, endtag(curState.startOfLine));
				}
				else if (type == "closeTag")
				{
					var err = false;
					if (curState.context)
					{
						if (curState.context.tagName != tagName)
						{
							if (arTags.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase()))
								popContext();
							err = !curState.context || curState.context.tagName != tagName;
						}
					}
					else
					{
						err = true;
					}
					if (err)
						setStyle = "error";
					return cont(endclosetag(err));
				}
				return cont();
			}

			function endtag(startOfLine)
			{
				return function(type)
				{
					if (type == "selfcloseTag" || (type == "endTag" && arTags.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
					{
						maybePopContext(curState.tagName.toLowerCase());
						return cont();
					}
					if (type == "endTag")
					{
						maybePopContext(curState.tagName.toLowerCase());
						pushContext(curState.tagName, startOfLine);
						return cont();
					}
					return cont();
				};
			}

			function endclosetag(err)
			{
				return function(type)
				{
					if (err)
						setStyle = "error";
					if (type == "endTag")
					{
						popContext();
						return cont();
					}
					setStyle = "error";
					return cont(arguments.callee);
				};
			}
			function maybePopContext(nextTagName)
			{
				var parentTagName;
				while (true)
				{
					if (!curState.context)
						return;
					parentTagName = curState.context.tagName.toLowerCase();
					if (!arTags.contextGrabbers.hasOwnProperty(parentTagName) || !arTags.contextGrabbers[parentTagName].hasOwnProperty(nextTagName))
						return;
					popContext();
				}
			}

			function attributes(type)
			{
				if (type == "word")
				{
					setStyle = "attribute";
					return cont(attribute, attributes);
				}

				if (type == "endTag" || type == "selfcloseTag")
					return pass();

				setStyle = "error";
				return cont(attributes);
			}

			function attribute(type)
			{
				if (type == "equals")
					return cont(attvalue, attributes);
				if (!arTags.allowMissing)
					setStyle = "error";
				return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
			}

			function attvalue(type)
			{
				if (type == "string")
					return cont(attvaluemaybe);
				if (type == "word" && arTags.allowUnquoted)
				{
					setStyle = "string";
					return cont();
				}
				setStyle = "error";
				return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
			}

			function attvaluemaybe(type)
			{
				return type == "string" ? cont(attvaluemaybe) : pass();
			}

			return {
				startStatus: function()
				{
					return {
						tokenize: InsideText,
						cc: [],
						indented: 0,
						startOfLine: true,
						tagName: null,
						context: null
					};
				},
				HandleChar: function(stream, status)
				{
					if (stream.sol())
					{
						status.startOfLine = true;
						status.indented = stream.indentation();
					}
					if (stream.eatSpace()) return null;

					setStyle = type = tagName = null;
					var style = status.tokenize(stream, status);
					status.type = type;
					if ((style || type) && style != "comment")
					{
						curState = status;
						while (true)
						{
							var comb = status.cc.pop() || element;
							if (comb(type || style))
								break;
						}
					}
					status.startOfLine = false;
					return setStyle || style;
				},

				Indent: function(status, textAfter, fullLine)
				{
					var context = status.context;
					if ((status.tokenize != InsideTag && status.tokenize != InsideText) || context && context.noIndent)
						return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;

					if (context && /^<\//.test(textAfter))
						context = context.prev;
					while (context && !context.startOfLine)
						context = context.prev;
					if (context)
						return context.indent + indentUnit;
					else
						return 0;
				},

				compareStates: function(a, b)
				{
					if (a.indented != b.indented || a.tokenize != b.tokenize)
						return false;
					for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev)
					{
						if (!ca || !cb)
							return ca == cb;
						if (ca.tagName != cb.tagName || ca.indent != cb.indent)
							return false;
					}
				},

				magicSym: "/"
			};
		};

		Syntaxes.js = function()
		{
			var indentUnit = 4;
			var parserConfig = {};
			var arKeywords = function()
			{
				function getKeyWord(type) {return {type: type, style: "keyword"};}
				var
					keyA = getKeyWord("keyword a"),
					keyB = getKeyWord("keyword b"),
					keyC = getKeyWord("keyword c"),
					keyVar = getKeyWord("var"),
					operator = getKeyWord("operator"),
					atom = {type: "atom", style: "atom"};

				return {
					"if": keyA, "while": keyA, "with": keyA,
					"else": keyB, "do": keyB, "try": keyB, "finally": keyB,
					"return": keyC, "break": keyC, "continue": keyC, "new": keyC, "delete": keyC, "throw": keyC,
					"var": keyVar, "const": keyVar, "let": keyVar,
					"function": getKeyWord("function"),
					"catch": getKeyWord("catch"), "for": getKeyWord("for"), "switch": getKeyWord("switch"), "case": getKeyWord("case"), "default": getKeyWord("default"),
					"in": operator, "typeof": operator, "instanceof": operator,
					"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
				};
			}();

			var isOperatorChar = /[+\-*&%=<>!?|]/;

			function chain(stream, status, f)
			{
				status.tokenize = f;
				return f(stream, status);
			}

			function nextUntilUnescaped(stream, end)
			{
				var escaped = false, next;
				while ((next = stream.next()) != null)
				{
					if (next == end && !escaped)
						return false;
					escaped = !escaped && next == "\\";
				}
				return escaped;
			}

			var type, content;
			function retStyle(tp, style, cont)
			{
				type = tp;
				content = cont;
				return style;
			}

			function jsTokenBase(stream, status)
			{
				var
					result,
					ch = stream.next();
				if (ch == '"' || ch == "'")
				{
					result = chain(stream, status, jsTokenString(ch));
				}
				else if (/[\[\]{}\(\),;\:\.]/.test(ch))
				{
					result = retStyle(ch);
				}
				else if (ch == "0" && stream.eat(/x/i))
				{
					stream.eatWhile(/[\da-f]/i);
					result = retStyle("number", "number");
				}
				else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/))
				{
					stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
					result = retStyle("number", "number");
				}
				else if (ch == "/")
				{
					if (stream.eat("*"))
					{
						result = chain(stream, status, jsTokenComment);
					}
					else if (stream.eat("/"))
					{
						stream.skipToEnd();
						result = retStyle("comment", "comment");
					}
					else if (status.reAllowed)
					{
						nextUntilUnescaped(stream, "/");
						stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
						result = retStyle("regexp", "string-2");
					}
					else
					{
						stream.eatWhile(isOperatorChar);
						result = retStyle("operator", null, stream.current());
					}
				}
				else if (ch == "#")
				{
					stream.skipToEnd();
					result = retStyle("error", "error");
				}
				else if (isOperatorChar.test(ch))
				{
					stream.eatWhile(isOperatorChar);
					result = retStyle("operator", null, stream.current());
				}
				else
				{
					stream.eatWhile(/[\w\$_]/);
					var
						word = stream.current(),
						known = arKeywords.propertyIsEnumerable(word) && arKeywords[word];
					result = known && status.kwAllowed ? retStyle(known.type, known.style || known.type, word) : retStyle("variable", "variable", word);
				}

				return result;
			}

			function jsTokenString(quote)
			{
				return function(stream, status)
				{
					if (!nextUntilUnescaped(stream, quote))
						status.tokenize = jsTokenBase;
					return retStyle("string", "string");
				};
			}

			function jsTokenComment(stream, status)
			{
				var maybeEnd = false, ch;
				while (ch = stream.next())
				{
					if (ch == "/" && maybeEnd)
					{
						status.tokenize = jsTokenBase;
						break;
					}
					maybeEnd = (ch == "*");
				}
				return retStyle("comment", "comment");
			}

			// Parser
			var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};

			function JSLexical(indented, column, type, align, prev, info)
			{
				this.indented = indented;
				this.column = column;
				this.type = type;
				this.prev = prev;
				this.info = info;
				if (align != null)
					this.align = align;
			}

			function inScope(status, varname)
			{
				for (var v = status.localVars; v; v = v.next)
					if (v.name == varname)
						return true;
			}

			function parseJS(status, style, type, content, stream)
			{
				var cc = status.cc;
				cx.status = status; cx.stream = stream; cx.marked = null, cx.cc = cc;

				if (!status.lexical.hasOwnProperty("align"))
					status.lexical.align = true;

				while(true)
				{
					var combinator = cc.length ? cc.pop() : statement;
					if (combinator(type, content))
					{
						while(cc.length && cc[cc.length - 1].lex)
							cc.pop()();
						if (cx.marked)
							return cx.marked;
						if (type == "variable" && inScope(status, content))
							return "variable-2";
						return style;
					}
				}
			}

			var cx = {status: null, column: null, marked: null, cc: null};
			function pass()
			{
				for (var i = arguments.length - 1; i >= 0; i--)
					cx.cc.push(arguments[i]);
			}

			function cont()
			{
				pass.apply(null, arguments);
				return true;
			}

			function register(varname)
			{
				var status = cx.status;
				if (status.context)
				{
					cx.marked = "def";
					for (var v = status.localVars; v; v = v.next)
						if (v.name == varname)
							return;
					status.localVars = {name: varname, next: status.localVars};
				}
			}

			// Combinators
			var defaultVars = {name: "this", next: {name: "arguments"}};

			function pushcontext()
			{
				if (!cx.status.context)
					cx.status.localVars = defaultVars;

				cx.status.context =
				{
					prev: cx.status.context,
					vars: cx.status.localVars
				};
			}

			function popcontext()
			{
				cx.status.localVars = cx.status.context.vars;
				cx.status.context = cx.status.context.prev;
			}

			function pushlex(type, info)
			{
				var result = function()
				{
					var status = cx.status;
					status.lexical = new JSLexical(status.indented, cx.stream.column(), type, null, status.lexical, info);
				};
				result.lex = true;
				return result;
			}

			function poplex()
			{
				var status = cx.status;
				if (status.lexical.prev)
				{
					if (status.lexical.type == ")")
						status.indented = status.lexical.indented;
					status.lexical = status.lexical.prev;
				}
			}
			poplex.lex = true;

			function expect(wanted)
			{
				return function expecting(type)
				{
					if (type == wanted)
						return cont();
					else if (wanted == ";")
						return pass();
					else
						return cont(arguments.callee);
				};
			}

			function statement(type)
			{
				if (type == "var")
					return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
				if (type == "keyword a")
					return cont(pushlex("form"), expression, statement, poplex);
				if (type == "keyword b")
					return cont(pushlex("form"), statement, poplex);
				if (type == "{")
					return cont(pushlex("}"), block, poplex);
				if (type == ";")
					return cont();
				if (type == "function")
					return cont(functiondef);
				if (type == "for")
					return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),poplex, statement, poplex);
				if (type == "variable")
					return cont(pushlex("stat"), maybelabel);
				if (type == "switch")
					return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex);
				if (type == "case")
					return cont(expression, expect(":"));
				if (type == "default")
					return cont(expect(":"));
				if (type == "catch")
					return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext);
				return pass(pushlex("stat"), expression, expect(";"), poplex);
			}

			function expression(type)
			{
				if (atomicTypes.hasOwnProperty(type))
					return cont(maybeoperator);
				if (type == "function")
					return cont(functiondef);
				if (type == "keyword c")
					return cont(maybeexpression);
				if (type == "(")
					return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
				if (type == "operator")
					return cont(expression);
				if (type == "[")
					return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
				if (type == "{")
					return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
				return cont();
			}
			function maybeexpression(type)
			{
				if (type.match(/[,;\}\)\]]/))
					return pass();
				return pass(expression);
			}

			function maybeoperator(type, value)
			{
				if (type == "operator" && /\+\+|--/.test(value))
					return cont(maybeoperator);
				if (type == "operator" && value == "?")
					return cont(expression, expect(":"), expression);
				if (type == ";")
					return;
				if (type == "(")
					return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
				if (type == ".")
					return cont(property, maybeoperator);
				if (type == "[")
					return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
			}

			function maybelabel(type)
			{
				if (type == ":")
					return cont(poplex, statement);
				return pass(maybeoperator, expect(";"), poplex);
			}

			function property(type)
			{
				if (type == "variable")
				{
					cx.marked = "property";
					return cont();
				}
			}

			function objprop(type)
			{
				if (type == "variable")
					cx.marked = "property";
				if (atomicTypes.hasOwnProperty(type))
					return cont(expect(":"), expression);
			}

			function commasep(what, end)
			{
				function proceed(type)
				{
					if (type == ",")
						return cont(what, proceed);
					if (type == end)
						return cont();
					return cont(expect(end));
				}

				return function commaSeparated(type)
				{
					if (type == end)
						return cont();
					else
						return pass(what, proceed);
				};
			}

			function block(type)
			{
				if (type == "}")
					return cont();
				return pass(statement, block);
			}

			function vardef1(type, value)
			{
				if (type == "variable")
				{
					register(value);
					return cont(vardef2);
				}
				return cont();
			}

			function vardef2(type, value)
			{
				if (value == "=")
					return cont(expression, vardef2);
				if (type == ",")
					return cont(vardef1);
			}

			function forspec1(type)
			{
				if (type == "var")
					return cont(vardef1, forspec2);
				if (type == ";")
					return pass(forspec2);
				if (type == "variable")
					return cont(formaybein);
				return pass(forspec2);
			}

			function formaybein(type, value)
			{
				if (value == "in")
					return cont(expression);
				return cont(maybeoperator, forspec2);
			}

			function forspec2(type, value)
			{
				if (type == ";")
					return cont(forspec3);
				if (value == "in")
					return cont(expression);
				return cont(expression, expect(";"), forspec3);
			}

			function forspec3(type)
			{
				if (type != ")")
					cont(expression);
			}

			function functiondef(type, value)
			{
				if (type == "variable")
				{
					register(value);
					return cont(functiondef);
				}

				if (type == "(")
					return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
			}

			function funarg(type, value)
			{
				if (type == "variable")
				{
					register(value);
					return cont();
				}
			}

			return {
				startStatus: function(basecolumn) {
					return {
						tokenize: jsTokenBase,
						reAllowed: true,
						kwAllowed: true,
						cc: [],
						lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
						localVars: parserConfig.localVars,
						context: parserConfig.localVars && {vars: parserConfig.localVars},
						indented: 0
					};
				},

				HandleChar: function(stream, status)
				{
					if (stream.sol())
					{
						if (!status.lexical.hasOwnProperty("align"))
							status.lexical.align = false;
						status.indented = stream.indentation();
					}

					if (stream.eatSpace())
						return null;
					var style = status.tokenize(stream, status);
					if (type == "comment")
						return style;
					status.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
					status.kwAllowed = type != '.';
					return parseJS(status, style, type, content, stream);
				},

				Indent: function(status, textAfter)
				{
					if (status.tokenize != jsTokenBase)
						return 0;
					var
						firstChar = textAfter && textAfter.charAt(0),
						lexical = status.lexical;

					if (lexical.type == "stat" && firstChar == "}")
						lexical = lexical.prev;

					var
						type = lexical.type,
						closing = firstChar == type,
						res = lexical.indented + (closing ? 0 : indentUnit);

					if (type == "vardef")
						res = lexical.indented + 4;
					else if (type == "form" && firstChar == "{")
						res = lexical.indented;
					else if (type == "stat" || type == "form")
						res = lexical.indented + indentUnit;
					else if (lexical.info == "switch" && !closing)
						res = lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
					else if (lexical.align)
						res = lexical.column + (closing ? 0 : 1);

					return res;
				},

				magicSym: ":{}"
			};
		};

		Syntaxes.sql = function()
		{
			var
				indentUnit = 4,
				curPunc,
				ops = new RegExp("^(?:" + "str|lang|langmatches|datatype|bound|sameterm|isiri|isuri|isblank|isliteral|union|a" + ")$", "i"),
				keywords = new RegExp("^(?:" + "ACCESSIBLE|ALTER|AS|BEFORE|BINARY|BY|CASE|CHARACTER|COLUMN|CONTINUE|CROSS|CURRENT_TIMESTAMP|DATABASE|DAY_MICROSECOND|DEC|DEFAULT|DESC|DISTINCT|DOUBLE|EACH|ENCLOSED|EXIT|FETCH|FLOAT8|FOREIGN|GRANT|HIGH_PRIORITY|HOUR_SECOND|IN|INNER|INSERT|INT2|INT8|INTO|JOIN|KILL|LEFT|LINEAR|LOCALTIME|LONG|LOOP|MATCH|MEDIUMTEXT|MINUTE_SECOND|NATURAL|NULL|OPTIMIZE|OR|OUTER|PRIMARY|RANGE|READ_WRITE|REGEXP|REPEAT|RESTRICT|RIGHT|SCHEMAS|SENSITIVE|SHOW|SPECIFIC|SQLSTATE|SQL_CALC_FOUND_ROWS|STARTING|TERMINATED|TINYINT|TRAILING|UNDO|UNLOCK|USAGE|UTC_DATE|VALUES|VARCHARACTER|WHERE|WRITE|ZEROFILL|ALL|AND|ASENSITIVE|BIGINT|BOTH|CASCADE|CHAR|COLLATE|CONSTRAINT|CREATE|CURRENT_TIME|CURSOR|DAY_HOUR|DAY_SECOND|DECLARE|DELETE|DETERMINISTIC|DIV|DUAL|ELSEIF|EXISTS|FALSE|FLOAT4|FORCE|FULLTEXT|HAVING|HOUR_MINUTE|IGNORE|INFILE|INSENSITIVE|INT1|INT4|INTERVAL|ITERATE|KEYS|LEAVE|LIMIT|LOAD|LOCK|LONGTEXT|MASTER_SSL_VERIFY_SERVER_CERT|MEDIUMINT|MINUTE_MICROSECOND|MODIFIES|NO_WRITE_TO_BINLOG|ON|OPTIONALLY|OUT|PRECISION|PURGE|READS|REFERENCES|RENAME|REQUIRE|REVOKE|SCHEMA|SELECT|SET|SPATIAL|SQLEXCEPTION|SQL_BIG_RESULT|SSL|TABLE|TINYBLOB|TO|TRUE|UNIQUE|UPDATE|USING|UTC_TIMESTAMP|VARCHAR|WHEN|WITH|YEAR_MONTH|ADD|ANALYZE|ASC|BETWEEN|BLOB|CALL|CHANGE|CHECK|CONDITION|CONVERT|CURRENT_DATE|CURRENT_USER|DATABASES|DAY_MINUTE|DECIMAL|DELAYED|DESCRIBE|DISTINCTROW|DROP|ELSE|ESCAPED|EXPLAIN|FLOAT|FOR|FROM|GROUP|HOUR_MICROSECOND|IF|INDEX|INOUT|INT|INT3|INTEGER|IS|KEY|LEADING|LIKE|LINES|LOCALTIMESTAMP|LONGBLOB|LOW_PRIORITY|MEDIUMBLOB|MIDDLEINT|MOD|NOT|NUMERIC|OPTION|ORDER|OUTFILE|PROCEDURE|READ|REAL|RELEASE|REPLACE|RETURN|RLIKE|SECOND_MICROSECOND|SEPARATOR|SMALLINT|SQL|SQLWARNING|SQL_SMALL_RESULT|STRAIGHT_JOIN|THEN|TINYTEXT|TRIGGER|UNION|UNSIGNED|USE|UTC_TIME|VARBINARY|VARYING|WHILE|XOR|FULL|COLUMNS|MIN|MAX|STDEV|COUNT" + ")$", "i"),
				operatorChars = /[*+\-<>=&|]/;

			function tokenBase(stream, status)
			{
				var ch = stream.next();
				curPunc = null;
				if (ch == "$" || ch == "?")
				{
					stream.match(/^[\w\d]*/);
					return "variable-2";
				}
				else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false))
				{
					stream.match(/^[^\s\u00a0>]*>?/);
					return "atom";
				}
				else if (ch == "\"" || ch == "'")
				{
					status.tokenize = tokenLiteral(ch);
					return status.tokenize(stream, status);
				}
				else if (ch == "`")
				{
					status.tokenize = tokenOpLiteral(ch);
					return status.tokenize(stream, status);
				}
				else if (/[{}\(\),\.;\[\]]/.test(ch))
				{
					curPunc = ch;
					return null;
				}
				else if (ch == "-")
				{
					var ch2 = stream.next();
					if (ch2=="-")
					{
						stream.skipToEnd();
						return "comment";
					}
				}
				else if (operatorChars.test(ch))
				{
					stream.eatWhile(operatorChars);
					return null;
				}
				else if (ch == ":")
				{
					stream.eatWhile(/[\w\d\._\-]/);
					return "atom";
				}
				else
				{
					stream.eatWhile(/[_\w\d]/);
					if (stream.eat(":"))
					{
						stream.eatWhile(/[\w\d_\-]/);
						return "atom";
					}

					var word = stream.current(), type;
					if (ops.test(word))
						return null;
					else if (keywords.test(word))
						return "keyword";
					else
						return "variable";
				}
			}

			function tokenLiteral(quote)
			{
				return function(stream, status)
				{
					var escaped = false, ch;
					while ((ch = stream.next()) != null)
					{
						if (ch == quote && !escaped)
						{
							status.tokenize = tokenBase;
							break;
						}
						escaped = !escaped && ch == "\\";
					}
					return "string";
				};
			}

			function tokenOpLiteral(quote)
			{
				return function(stream, status)
				{
					var escaped = false, ch;
					while ((ch = stream.next()) != null)
					{
						if (ch == quote && !escaped)
						{
							status.tokenize = tokenBase;
							break;
						}
						escaped = !escaped && ch == "\\";
					}
					return "variable-2";
				};
			}


			function pushContext(status, type, col)
			{
				status.context = {prev: status.context, indent: status.indent, col: col, type: type};
			}

			function popContext(status)
			{
				status.indent = status.context.indent;
				status.context = status.context.prev;
			}

			return {
				startStatus: function(base)
				{
					return {
						tokenize: tokenBase,
						context: null,
						indent: 0,
						col: 0
					};
				},

				HandleChar: function(stream, status)
				{
					if (stream.sol())
					{
						if (status.context && status.context.align == null)
							status.context.align = false;
						status.indent = stream.indentation();
					}
					if (stream.eatSpace())
						return null;
					var style = status.tokenize(stream, status);

					if (style != "comment" && status.context && status.context.align == null && status.context.type != "pattern")
						status.context.align = true;

					if (curPunc == "(")
					{
						pushContext(status, ")", stream.column());
					}
					else if (curPunc == "[")
					{
						pushContext(status, "]", stream.column());
					}
					else if (curPunc == "{")
					{
						pushContext(status, "}", stream.column());
					}
					else if (/[\]\}\)]/.test(curPunc))
					{
						while (status.context && status.context.type == "pattern")
							popContext(status);
						if (status.context && curPunc == status.context.type)
							popContext(status);
					}
					else if (curPunc == "." && status.context && status.context.type == "pattern")
					{
						popContext(status);
					}
					else if (/atom|string|variable/.test(style) && status.context)
					{
						if (/[\}\]]/.test(status.context.type))
						{
							pushContext(status, "pattern", stream.column());
						}
						else if (status.context.type == "pattern" && !status.context.align)
						{
							status.context.align = true;
							status.context.col = stream.column();
						}
					}

					return style;
				},

				Indent: function(status, textAfter)
				{
					var
						firstChar = textAfter && textAfter.charAt(0),
						context = status.context;

					if (/[\]\}]/.test(firstChar))
					{
						while (context && context.type == "pattern")
							context = context.prev;
					}

					var
						closing = context && firstChar == context.type,
						result = context.indent + (closing ? 0 : indentUnit);

					if (!context)
						result = 0;
					else if (context.type == "pattern")
						result = context.col;
					else if (context.align)
						result = context.col + (closing ? 0 : 1);

					return result;
				}
			};
		};

		Syntaxes.phpcore = function()
		{
			var
				indentUnit = 4,
				keywords =  prepareKeywords(['echo', 'include', 'require', 'include_once', 'require_once','for', 'foreach', 'as', 'endswitch', 'return', 'break', 'continue', 'null', '__LINE__', '__FILE__', 'var', 'default', 'function', 'class', 'new', '&amp;new', 'this', '__FUNCTION__', '__CLASS__', '__METHOD__', 'PHP_VERSION', 'E_ERROR', 'E_WARNING','E_PARSE', 'E_NOTICE', 'E_CORE_ERROR', 'E_CORE_WARNING', 'E_COMPILE_ERROR', 'E_COMPILE_WARNING', 'E_USER_ERROR', 'E_USER_WARNING', 'E_USER_NOTICE', 'E_ALL', 'abstract', 'array']),
				blockKeywords = prepareKeywords(['catch', 'do', 'else', 'elseif', 'for', 'foreach', 'if', 'switch', 'try', 'while', 'endwhile', 'endif', 'case']),
				atoms = prepareKeywords(['true', 'false', 'null', 'TRUE', 'FALSE', 'NULL']),
				hooks = {
					"$": function (stream, status)
					{
						stream.eatWhile(/[\w\$_]/);
						return "variable-2";
					},
					"<": function (stream, status)
					{
						if (stream.match(/<</))
						{
							stream.eatWhile(/[\w\.]/);
							var delimiter = stream.current().slice(3);
							status.tokenize = function (stream, status)
							{
								if (stream.match(delimiter))
									status.tokenize = null;
								else
									stream.skipToEnd();
								return "string";
							};
							return status.tokenize(stream, status);
						}
						return false;
					},
					"#": function (stream, status)
					{
						while (!stream.eol() && !stream.match("?>", false))
							stream.next();
						return "comment";
					},
					"/": function (stream, status)
					{
						if (stream.eat("/"))
						{
							while (!stream.eol() && !stream.match("?>", false)) stream.next();
							return "comment";
						}
						return false;
					}
				},
				multiLineStrings = true,
				isOperatorChar = /[+\-*&%=<>!?|\/]/,
				curPunc;

			function tokenBase(stream, status)
			{
				var ch = stream.next();
				if (hooks[ch])
				{
					var result = hooks[ch](stream, status);
					if (result !== false)
						return result;
				}

				if (ch == '"' || ch == "'")
				{
					status.tokenize = tokenString(ch);
					return status.tokenize(stream, status);
				}
				if (/[\[\]{}\(\),;\:\.]/.test(ch))
				{
					curPunc = ch;
					return null;
				}
				if (/\d/.test(ch))
				{
					stream.eatWhile(/[\w\.]/);
					return "number";
				}
				if (ch == "/")
				{
					if (stream.eat("*"))
					{
						status.tokenize = tokenComment;
						return tokenComment(stream, status);
					}
					if (stream.eat("/"))
					{
						stream.skipToEnd();
						return "comment";
					}
				}
				if (isOperatorChar.test(ch))
				{
					stream.eatWhile(isOperatorChar);
					return "operator";
				}
				stream.eatWhile(/[\w\$_]/);
				var cur = stream.current();
				if (keywords.propertyIsEnumerable(cur))
				{
					if (blockKeywords.propertyIsEnumerable(cur))
						curPunc = "newstatement";
					return "keyword";
				}
				if (atoms.propertyIsEnumerable(cur))
					return "atom";
				return "variable";
			}

			function tokenString(quote)
			{
				return function(stream, status)
				{
					var
						escaped = false,
						next,
						end = false;
					while ((next = stream.next()) != null)
					{
						if (next == quote && !escaped)
						{
							end = true;
							break;
						}
						escaped = !escaped && next == "\\";
					}
					if (end || !(escaped || multiLineStrings))
						status.tokenize = null;
					return "string";
				};
			}

			function tokenComment(stream, status)
			{
				var
					maybeEnd = false,
					ch;
				while (ch = stream.next())
				{
					if (ch == "/" && maybeEnd)
					{
						status.tokenize = null;
						break;
					}
					maybeEnd = (ch == "*");
				}
				return "comment";
			}

			function JCContext(indented, column, type, align, prev)
			{
				this.indented = indented;
				this.column = column;
				this.type = type;
				this.align = align;
				this.prev = prev;
			}

			function pushContext(status, col, type)
			{
				return status.context = new JCContext(status.indented, col, type, null, status.context);
			}

			function popContext(status)
			{
				var t = status.context.type;
				if (t == ")" || t == "]" || t == "}")
					status.indented = status.context.indented;
				return status.context = status.context.prev;
			}

			// Interface
			return {
				startStatus: function(basecolumn)
				{
					return {
						tokenize: null,
						context: new JCContext((basecolumn || 0) - indentUnit, 0, "top", false),
						indented: 0,
						startOfLine: true
					};
				},

				HandleChar: function(stream, status)
				{
					var ctx = status.context;
					if (stream.sol())
					{
						if (ctx.align == null)
							ctx.align = false;
						status.indented = stream.indentation();
						status.startOfLine = true;
					}
					if (stream.eatSpace())
						return null;
					curPunc = null;
					var style = (status.tokenize || tokenBase)(stream, status);

					if (style == "comment" || style == "meta")
						return style;
					if (ctx.align == null)
						ctx.align = true;

					if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement")
					{
						popContext(status);
					}
					else if (curPunc == "{")
					{
						pushContext(status, stream.column(), "}");
					}
					else if (curPunc == "[")
					{
						pushContext(status, stream.column(), "]");
					}
					else if (curPunc == "(")
					{
						pushContext(status, stream.column(), ")");
					}
					else if (curPunc == "}")
					{
						while (ctx.type == "statement")
							ctx = popContext(status);
						if (ctx.type == "}")
							ctx = popContext(status);
						while (ctx.type == "statement")
							ctx = popContext(status);
					}
					else if (curPunc == ctx.type)
					{
						popContext(status);
					}
					else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
					{
						pushContext(status, stream.column(), "statement");
					}
					status.startOfLine = false;
					return style;
				},

				Indent: function(status, textAfter)
				{
					if (status.tokenize != tokenBase && status.tokenize != null)
						return 0;
					var
						ctx = status.context,
						firstChar = textAfter && textAfter.charAt(0);

					if (ctx.type == "statement" && firstChar == "}")
						ctx = ctx.prev;

					if (ctx.type == "statement")
						return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
					else if (ctx.align)
						return ctx.column + (firstChar == ctx.type ? 0 : 1);
					else
						return ctx.indented + (firstChar == ctx.type ? 0 : indentUnit);
				},

				magicSym: "{}"
			};
		};

		Syntaxes.css = function()
		{
			var indentUnit = 4, type;
			var keywords = prepareKeywords(["above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", "arabic-indic", "armenian", "asterisks","auto", "avoid", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button", "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu", "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default", "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help", "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "line-through", "linear", "lines", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", "lower-roman", "lowercase", "ltr", "malayalam", "match", "medium", "menu", "menulist", "menulist-button", "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "single", "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow","small", "small-caps", "small-caption", "smaller", "solid", "somali", "source-atop", "source-in", "source-out", "source-over", "space", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super", "sw-resize", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext", "x-large", "x-small", "xor", "xx-large", "xx-small", "yellow"]);

			function retStyle(style, tp)
			{
				type = tp;
				return style;
			}

			function tokenBase(stream, status)
			{
				var ch = stream.next();
				if (ch == "@")
				{
					stream.eatWhile(/[\w\\\-]/);
					return retStyle("meta", stream.current());
				}
				else if (ch == "/" && stream.eat("*"))
				{
					status.tokenize = tokenCComment;
					return tokenCComment(stream, status);
				}
				else if (ch == "<" && stream.eat("!"))
				{
					status.tokenize = tokenSGMLComment;
					return tokenSGMLComment(stream, status);
				}
				else if (ch == "=")
				{
					return retStyle(null, "compare");
				}
				else if ((ch == "~" || ch == "|") && stream.eat("="))
				{
					return retStyle(null, "compare");
				}
				else if (ch == "\"" || ch == "'")
				{
					status.tokenize = tokenString(ch);
					return status.tokenize(stream, status);
				}
				else if (ch == "#")
				{
					stream.eatWhile(/[\w\\\-]/);
					return retStyle("atom", "hash");
				}
				else if (ch == "!")
				{
					stream.match(/^\s*\w*/);
					return retStyle("keyword", "important");
				}
				else if (/\d/.test(ch))
				{
					stream.eatWhile(/[\w.%]/);
					return retStyle("number", "unit");
				}
				else if (/[,.+>*\/]/.test(ch))
				{
					return retStyle(null, "select-op");
				}
				else if (/[;{}:\[\]\(\)]/.test(ch))
				{
					return retStyle(null, ch);
				}
				else
				{
					stream.eatWhile(/[\w\\\-]/);
					return retStyle("variable", "variable");
				}
			}

			function tokenCComment(stream, status)
			{
				var maybeEnd = false, ch;
				while ((ch = stream.next()) != null)
				{
					if (maybeEnd && ch == "/")
					{
						status.tokenize = tokenBase;
						break;
					}
					maybeEnd = (ch == "*");
				}
				return retStyle("comment", "comment");
			}

			function tokenSGMLComment(stream, status)
			{
				var dashes = 0, ch;
				while ((ch = stream.next()) != null)
				{
					if (dashes >= 2 && ch == ">")
					{
						status.tokenize = tokenBase;
						break;
					}
					dashes = (ch == "-") ? dashes + 1 : 0;
				}
				return retStyle("comment", "comment");
			}

			function tokenString(quote)
			{
				return function(stream, status)
				{
					var escaped = false, ch;
					while ((ch = stream.next()) != null)
					{
						if (ch == quote && !escaped)
							break;
						escaped = !escaped && ch == "\\";
					}
					if (!escaped)
						status.tokenize = tokenBase;
					return retStyle("string", "string");
				};
			}

			return {
				startStatus: function(base)
				{
					return {tokenize: tokenBase,
						baseIndent: base || 0,
						stack: []};
				},

				HandleChar: function(stream, status)
				{
					if (stream.eatSpace())
						return null;
					var style = status.tokenize(stream, status);

					var context = status.stack[status.stack.length-1];
					if (type == "hash" && context != "rule")
					{
						style = "string-2";
					}
					else if (style == "variable")
					{
						if (context == "rule")
							style = keywords[stream.current()] ? "keyword" : "number";
						else if (!context || context == "@media{")
							style = "tag";
					}

					if (context == "rule" && /^[\{\};]$/.test(type))
					{
						status.stack.pop();
					}
					if (type == "{")
					{
						if (context == "@media")
							status.stack[status.stack.length-1] = "@media{";
						else
							status.stack.push("{");
					}
					else if (type == "}")
					{
						status.stack.pop();
					}
					else if (type == "@media")
					{
						status.stack.push("@media");
					}
					else if (context == "{" && type != "comment")
					{
						status.stack.push("rule");
					}
					return style;
				},

				Indent: function(status, textAfter)
				{
					var n = status.stack.length;
					if (/^\}/.test(textAfter))
						n -= status.stack[status.stack.length-1] == "rule" ? 2 : 1;
					return status.baseIndent + n * indentUnit;
				},

				magicSym: "}"
			};
		};

		Syntaxes.php = function ()
		{
			var
				parserConfig = {},
				syntHtml = Syntaxes.html(),
				syntJs = Syntaxes.js(),
				syntCss = Syntaxes.css(),
				syntPhp = Syntaxes.phpcore();

			function dispatch(stream, status)
			{
				var
					style,
					isPHP = status.syntax == "php";

				if (stream.sol() && status.pending != '"')
					status.pending = null;

				if (status.curSyntax == syntHtml)
				{
					if (stream.match(/^<\?\w*/))
					{
						status.curSyntax = syntPhp;
						status.curState = status.php;
						status.curClose = "?>";
						status.syntax = "php";
						return "meta";
					}

					if (status.pending == '"')
					{
						while (!stream.eol() && stream.next() != '"')
						{
						}
						style = "string";
					}
					else if (status.pending && stream.pos < status.pending.end)
					{
						stream.pos = status.pending.end;
						style = status.pending.style;
					}
					else
					{
						style = syntHtml.HandleChar(stream, status.curState);
					}

					status.pending = null;
					var
						cur = stream.current(),
						openPHP = cur.search(/<\?/);
					if (openPHP != -1)
					{
						if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur))
							status.pending = '"';
						else
							status.pending = {end: stream.pos, style: style};
						stream.backUp(cur.length - openPHP);
					}
					else if (style == "tag" && stream.current() == ">" && status.curState.context)
					{
						if (/^script$/i.test(status.curState.context.tagName))
						{
							status.curSyntax = syntJs;
							status.curState = syntJs.startStatus(syntHtml.Indent(status.curState, ""));
							status.curClose = /^<\/\s*script\s*>/i;
							status.syntax = "js";
						}
						else if (/^style$/i.test(status.curState.context.tagName))
						{
							status.curSyntax = syntCss;
							status.curState = syntCss.startStatus(syntHtml.Indent(status.curState, ""));
							status.curClose = /^<\/\s*style\s*>/i;
							status.syntax = "css";
						}
					}
					return style;
				}
				else if ((!isPHP || status.php.tokenize == null) && stream.match(status.curClose, isPHP))
				{
					status.curSyntax = syntHtml;
					status.curState = status.html;
					status.curClose = null;
					status.syntax = "html";
					return isPHP ? "meta" : dispatch(stream, status);
				}
				else
				{
					return status.curSyntax.HandleChar(stream, status.curState);
				}
			}

			return {
				startStatus: function ()
				{
					var html = syntHtml.startStatus();
					return {
						html: html,
						php: syntPhp.startStatus(),
						curSyntax: parserConfig.startOpen ? syntPhp : syntHtml,
						curState: parserConfig.startOpen ? syntPhp.startStatus() : html,
						curClose: parserConfig.startOpen ? /^\?>/ : null,
						syntax: parserConfig.startOpen ? "php" : "html",
						pending: null
					};
				},

				copyStatus: function (status)
				{
					var
						html = status.html,
						htmlNew = copySyntaxStatus(syntHtml, html),
						php = status.php,
						phpNew = copySyntaxStatus(syntPhp, php),
						cur;

					if (status.curState == html)
						cur = htmlNew;
					else if (status.curState == php)
						cur = phpNew;
					else
						cur = copySyntaxStatus(status.curSyntax, status.curState);

					return {
						html: htmlNew,
						php: phpNew,
						curSyntax: status.curSyntax,
						curState: cur,
						curClose: status.curClose,
						syntax: status.syntax,
						pending: status.pending
					};
				},

				HandleChar: dispatch,

				Indent: function (status, textAfter)
				{
					if ((status.curSyntax != syntPhp && /^\s*<\//.test(textAfter)) || (status.curSyntax == syntPhp && /^\?>/.test(textAfter)))
						return syntHtml.Indent(status.html, textAfter);
					return status.curSyntax.Indent(status.curState, textAfter);
				},

				magicSym: "/{}:"
			};
		};
	}
	// -- ************* Syntaxes defenitions *************

	function JCHighlightedText(from, to, className, marker)
	{
		this.from = from;
		this.to = to;
		this.style = className;
		this.marker = marker;
	}

	JCHighlightedText.prototype = {
		attach: function (line)
		{
			this.marker.set.push(line);
		},
		detach: function (line)
		{
			var ix = indexOf(this.marker.set, line);
			if (ix > -1)
				this.marker.set.splice(ix, 1);
		},
		split: function (pos, lenBefore)
		{
			if (this.to <= pos && this.to != null)
				return null;
			var
				from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore,
				to = this.to == null ? null : this.to - pos + lenBefore;
			return new JCHighlightedText(from, to, this.style, this.marker);
		},
		dup: function ()
		{
			return new JCHighlightedText(null, null, this.style, this.marker);
		},
		clipTo: function (fromOpen, from, toOpen, to, diff)
		{
			if (fromOpen && to > this.from && (to < this.to || this.to == null))
				this.from = null;
			else if (this.from != null && this.from >= from)
				this.from = Math.max(to, this.from) + diff;
			if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
				this.to = null;
			else if (this.to != null && this.to > from)
				this.to = to < this.to ? this.to + diff : from;
		},
		isDead: function ()
		{
			return this.from != null && this.to != null && this.from >= this.to;
		},
		sameSet: function (x)
		{
			return this.marker == x.marker;
		}
	};

})();