/*
NAME Console
USES Core
DESCRIPTION "Эмуляция терминала с управлением клавиатурой. Поддерживает основной функционал Pascal.CRT. Не работает с браузерами на движке IE. Необходимо наличие функции $, возвращающей элемент по его идентификатору"
AUTHOR "Berserker"
EDITOR: "TAB 4, AUTOIDENT, MONOSPACE"
*/

/*
[TYPE]
[DESC] Атрибуты ячейки консоли
TAttr = RECORD
	Col:	STRING;	// Цвет символа
	Back:	STRING;	// Фон ячейки
	Ch:		CHAR;	// Символ
END;

[DESC] Результат работы функции подгона прямоугольника под текущее окно (выделение видимой его части)
TClipRect = RECORD
	x1, y1, x2, y2: INTEGER;	// Границы прямоугольника
	result: boolean;			// Флаг успешного подгона. Если он равен false, то остальные поля структуры могут отсутствовать
END;

[CONVENTION Rectangle] "Функция проверяет прямоугольник на корректность и берёт из него только ту часть, которая находится в текущем окне"

[CONVENTION CallBack] "Функция возвратит результат не прямо, а асинхронно, вызвав функцию CallBack с одним параметром-результатом"

[CONVENTION WaitUntilStopped] "Функции из данной серии нельзя вызывать повторно, пока они не вернули асинхронный результат, иначе происходит исключение ASSERT"

[DESC] Обработчик клавиатурных событий
TEventHandler = FUNCTION
(
	e,					// Объект event
	Stage: TEventStage	// Код события
);

[CONVENTION EventHandler] "Функция является обработчиком клавиатурных событий. Её тип - TEventHandler"
*/

// [DESC] Объявление констант для модуля
function Const (ConstHash)
{
	for (Key in ConstHash)
	{
		Console.prototype[Key] = ConstHash[Key];
	}
} // .function Const

/*
[DESC] Конструктор новой консоли
[ARG Elem]							HTML-контейнер для хранения содержимого консоли. Чаще всего div или span
[ARG IdPrefix]						Префикс для DOM ID элементов консоли (ячеек и поля ввода)
[ARG Width, Height = 80, 25]		Ширина и высота консоли в ячейках
[ARG Col, Back = 'white', 'black']	Цвет и фон ячеек консоли
[ARG Cursor = true]					Флаг видимости курсора
[ARG FontSize = '12pt']				CSS-значение размера шрифта
*/
function Console (Elem, IdPrefix, Width, Height, Col, Back, Cursor, FontSize)
{
	var	x = -1,
		y = -1,
		Html = [];
	Width = Core.GetValue(Width, 80);
	Height = Core.GetValue(Height, 25);
	Col = Core.GetValue(Col, 'white');
	Back = Core.GetValue(Back, 'black');
	Cursor = Core.GetValue(Cursor, true);
	FontSize = Core.GetValue(FontSize, '12pt');
	this.x = 0;						// Координаты курсора относительно текущего окна консоли
	this.y = 0;						// ...
	this.Width = Width;				// Ширина консоли в ячейках. Обычно 80
	this.Height = Height;			// Высота консоли в ячейках. Обычно 25
	this.Col = Col;					// Цвет текста в консоли. Используется для выводимого в будущем текста
	this.Back = Back;				// Фон консоли. Используется для выводимого в будущем текста
	this.Cursor = Cursor;			// Отображать ли курсор
	this.ModeReplace = false;		// Включён ли режим замены текста вместо вставки. Влияет на отображение курсора и функцию Read
	this.Rows = {};					// Строки консоли. Хранят массивы атрибутов (информации о каждой ячейке строки)
	this.IdPrefix = IdPrefix;		// Префикс для id input-элемента и элементов-ячеек матрицы консоли
	this.Elem = Elem;				// Контейнер для визуального отображения консоли
	this.Handlers = [];				// Массив обработчиков событий консоли. Верхний элемент - текущий обработчик
	this.Handler = null,			// Текущий вызываемый обработчик. Нужен для того, чтобы обработчик мог обращаться к консоли через this
	this.HandlerStopped = true;		// Флаг для функций консоли, служащий для запрета вторичного вызова асинхронных консольных функций во время их выполнения
	this.CallBack = null;			// Функция обратного вызова. Используется асинхронными консольными функциями для возврата значений пользовательскому коду
	this.Input = null;				// HTML-элемент, служащий для перехвата клавиатурного ввода
	this.x1 = 0;					// Границы текущего окна в консоли. Все функции работают только в пределах текущего окна
	this.y1 = 0;					// ...
	this.x2 = Width - 1;			// ...
	this.y2 = Height - 1;			// ...
	this.WndWidth = Width;			// Размеры текущего окна внутри консоли
	this.WndHeight = Height;		// ...
	this.MinLen = 0;				// Минимальный размер вводимого текста для функции Read
	this.MaxLen = 0;				// Максимальный размер вводимого текста для функции Read
	this.Buf = '';					// Буфер для вводимых символов
	this.BufPos = 0;				// Позиция в буфере ввода
	this.InpBuf = '';				// Буфер для хранения мульти-ввода. Используется функциями ReadEx/Read и FlushInputBuffer
	this.SavedCursor = true;		// Сохранённое состояние курсора до вызова функций ввода и редактирования текста
	this.LastStage = this.KEYUP;	// Код предыдущего события клавиатуры. Хранит только базовые значения (1-3)
	for (y = 0; y < Height; y++)
	{
		this.Rows[y] = [];
		for (x = 0; x < Width; x++)
		{
			this.Rows[y].push({Back: Back, Col: Col, Ch: ' '});
			Html.push('<span id="' + this.GetCellId(x, y) + '">' + ' ' + '</span>');
		}
		if ((y + 1) < Height)
		{
			Html.push('<br>');
		}
	}
	Input = document.createElement('textarea');
	Input.id = IdPrefix + 'Inp';
	Input.cols = 1;
	Input.rows = 1;
	Input.style.cssText = 'position: absolute; left: -1000px;';
	Input.Con = this;
	Input.Elem = Elem;
	Input.value = '';
	Input.onkeydown = function (e) {return this.Con._DefEventHandler(e, this.Con.KEYDOWN);};
	Input.onkeypress = function (e) {return this.Con._DefEventHandler(e, this.Con.KEYPRESS);};
	Input.onkeyup = function (e) {return this.Con._DefEventHandler(e, this.Con.KEYUP);};
	Input.onfocus = function (e) {this.Elem.style.border='2px solid red';};
	Input.onblur = function (e) {this.Elem.style.border='0px';};
	Elem.parentNode.insertBefore(Input, Elem);
	this.Input = Input;	
	this.Handlers.push(function () {return false;});	// Регистрация нижнего обработчика событий по умолчанию
	Elem.style.cssText	=	'display: inline-block; font-family: monospace; font-size: ' + Core.GetValue(FontSize, '12pt')
							+ '; background-color: ' + Back + '; white-space: pre; color: ' + Col + '; cursor: default;';
	Elem.Con = this;
	Elem.Input = Input;
	Elem.onclick = function (e) {this.Con.Input.focus(); return false;}
	Elem.innerHTML = Html.join('');
	this.DrawCursor();
	Elem.onclick();
} // .function Console

// [DESC TEventStage] Код клавиатурного события
Const
({
	KEYDOWN:	1,
	KEYPRESS:	2,
	KEYUP:		3,
	INPUT:		4
});

// [DESC] Коды распространённых клавиш
Const
({
	BACKSPACE:	8,
	TAB:		9,
	ENTER:		13,
	SHIFT:		16,
	CTRL:		17,
	ALT:		18,
	LEFT:		37,
	RIGHT:		39,
	UP:			38,
	DOWN:		40,
	INSERT:		45,
	DELETE:		46,
	HOME:		36,
	END:		35
});

// [DESC] Список особых клавиш, обрабатываемых функциями ReadEx/Read
Console.prototype.SpecialKeys =
{
	8:	1,	// Backspace
	13:	1,	// Enter
	35: 1,	// End
	36: 1,	// Home
	37: 1,	// Left
	38: 1,	// Up
	39: 1,	// Right
	40: 1,	// Down
	45: 1,	// Insert
	46: 1	// Delete
};

// [DESC] Список системных клавиш, обработка которых не должна быть запрещена
Console.prototype.SystemKeys =
{
	16: 1,	// Shift
	17: 1,	// Ctrl
	18: 1	// Alt
};

// [DESC] Уничтожает консоль
Console.prototype.Free = function ()
{
	this.Input.parentNode.removeChild(this.Input);
	this.Elem.style.border = '0px';
	this.Elem.innerHTML = '';
	this.Rows = null;
	this.Input = null;
	this.Elem = null;
} // .function Console.Free

// [DESC] Получить идентификатор ячейки консоли по её абсолютным координатам
Console.prototype.GetCellId = function (X, Y)
{
	return this.IdPrefix + X + ';' + Y;
} // .function Console.GetCellId

/*------------------------  РАБОТА  С  КУРСОРОМ  -----------------------------*/

// [DESC] Удаляет графическое отображение курсора
Console.prototype.EraseCursor = function ()
{
	var	x = this.x1 + this.x,
		y = this.y1 + this.y,
		Cell = $(this.GetCellId(x, y));
	Cell.style.textDecoration = 'inherit';
	Cell.textContent = this.Rows[y][x].Ch;
} // .function Console.EraseCursor

// [DESC] Осуществляет перерисовку курсора
Console.prototype.DrawCursor = function ()
{
	var	x = this.x1 + this.x,
		y = this.y1 + this.y,
		Cell = $(this.GetCellId(x, y));
	if (this.Cursor)
	{
		if (this.Rows[y][x].Ch === ' ')
		{
			Cell.textContent = '_';
			Cell.style.textDecoration = 'blink';
		}
		else if (this.ModeReplace)
		{
			Cell.style.textDecoration = 'line-through';
		}
		else
		{
			Cell.style.textDecoration = 'underline';
		}
	}
} // .function Console.DrawCursor

/*
[DESC] Изменяет видимость курсора
[ARG Cursor] Флаг видимости курсора
*/
Console.prototype.SetCursorVisibility = function (Cursor)
{
	if (this.Cursor !== Cursor)
	{
		this.Cursor = Cursor;
		if (Cursor)
		{
			this.DrawCursor();
		}
		else
		{
			this.EraseCursor();
		}
		this.Cursor = Cursor;
	}
} // .function Console.SetCursorVisibility

// [DESC] Переместить курсор в новую позицию.
Console.prototype.GotoXY = function (X, Y)
{
	if ((X !== this.x) || (Y !== this.y))
	{
		if (this.Cursor)
		{
			this.EraseCursor();
		}
		this.x = X;
		this.y = Y;
		this.DrawCursor();
	}
} // .function Console.GotoXY
/*----------------------------------------------------------------------------*/


/*----------------------  ФОРМАТ ВЫВОДИМОГО ТЕКСТА  --------------------------*/

// [DESC] Устанавливает цвет для выводимых символов
Console.prototype.SetCol = function (NewCol)
{
	this.Col = NewCol;
} // .function Console.SetCol

// [DESC] Устанавливает фон для выводимых символов
Console.prototype.SetBack = function (NewBack)
{
	this.Back = NewBack;
} // .function Console.SetBack

// [DESC] Устанавливает цвет и фон для выводимых символов
Console.prototype.SetAttr = function (NewCol, NewBack)
{
	this.Col = NewCol;
	this.Back = NewBack;
} // .function Console.SetAttr

/*----------------------------------------------------------------------------*/


/*--------------------  РАБОТА С ОТДЕЛЬНЫМИ ЯЧЕЙКАМИ  ------------------------*/

// [DESC] Рисует символ по указанной координате используя настройки цвета и фона консоли
Console.prototype.WriteChar = function (X, Y, Ch)
{
	var	x = this.x1 + X,
		y = this.y1 + Y,
		Cell = $(this.GetCellId(x, y)),
		Item = this.Rows[y][x];
	Cell.style.backgroundColor = this.Back;
	Cell.style.color = this.Col;
	Cell.textContent = Ch;
	Item.Back = this.Back;
	Item.Col = this.Col;
	Item.Ch = Ch;
	if ((this.x === X) && (this.y === Y))
	{
		this.DrawCursor();
	}
} // .function Console.WriteChar

// [DESC] Возвращает символ из указанной ячейки текущего окна
Console.prototype.CharAt = function (X, Y)
{
	return this.Rows[this.y1 + Y][this.x1 + X].Ch;
} // .function Console.CharAt

// [DESC] Копирует значение одной ячейки вместе с её атрибутами в другую
Console.prototype.CopyChar = function (SrcX, SrcY, DstX, DstY)
{
	var	X1 = this.x1 + SrcX,
		Y1 = this.y1 + SrcY,
		X2 = this.x1 + DstX,
		Y2 = this.y1 + DstY,
		SrcCell = $(this.GetCellId(X1, Y1)),
		SrcItem = this.Rows[Y1][X1],
		DstCell = $(this.GetCellId(X2, Y2)),
		DstItem = this.Rows[Y2][X2];
	DstCell.style.backgroundColor = SrcItem.Back;
	DstCell.style.color = SrcItem.Col;
	DstCell.textContent = SrcCell.textContent;
	DstItem.Back = SrcItem.Back;
	DstItem.Col = SrcItem.Col;
	DstItem.Ch = SrcItem.Ch;
	if ((DstX === this.x) && (DstY === this.y))
	{
		this.DrawCursor();
	}
} // .function Console.CopyChar

/*
[DESC] Изменяет в ячейке определённые атрибуты, которые есть в объекте Attr (любая комбинация из Col, Back, Ch)
[ARG Attr: TAttr] Любые поля структуры могут отсутствовать. При этом они не меняются в ячейке
*/
Console.prototype.WriteAttr = function (X, Y, Attr)
{
	var	x = this.x1 + X,
		y = this.y1 + Y,
		Cell = $(this.GetCellId(x, y)),
		Item = this.Rows[y][x];
	if (Attr.Back !== undefined)
	{
		Cell.style.backgroundColor = Attr.Back;
		Item.Back = Attr.Back;
	}
	if (Attr.Col !== undefined)
	{
		Cell.style.color = Attr.Col;
		Item.Col = Attr.Col;
	}
	if (Attr.Ch !== undefined)
	{
		Cell.textContent = Attr.Ch;
		Item.Ch = Attr.Ch;
		if ((this.x === X) && (this.y === Y))
		{
			this.DrawCursor();
		}
	}
} // .function Console.WriteAttr

/*
[DESC] Возвращает объект с атрибутами ячейки
[RET] TAttr
*/
Console.prototype.ReadAttr = function (X, Y)
{
	var Item = this.Rows[this.y1 + Y][this.x1 + X];
	return {Back: Item.Back, Col: Item.Col, Ch: Item.Ch};
} // .function Console.ReadAttr

/*
[DESC] Копирует указанные атрибуты одной ячейки в другую
[ARG Attr] Используется только факт отсутствия/наличия полей в структуре для указания того, какие именно атрибуты нужно скопировать
*/
Console.prototype.CopyAttr = function (SrcX, SrcY, DstX, DstY, Attr)
{
	var	X1 = this.x1 + SrcX,
		Y1 = this.y1 + SrcY,
		X2 = this.x1 + DstX,
		Y2 = this.y2 + DstY,
		SrcCell = $(this.GetCellId(X1, Y1)),
		SrcItem = this.Rows[Y1][X1],
		DstCell = $(this.GetCellId(X2, Y2)),
		DstItem = this.Rows[Y2][X2];
	if (Attr.Back !== undefined)
	{
		DstCell.style.backgroundColor = SrcItem.Back;
		DstItem.Back = SrcItem.Back;
	}
	if (Attr.Col !== undefined)
	{
		DstCell.style.color = SrcItem.Col;
		DstItem.Col = SrcItem.Col;
	}
	if (Attr.Ch !== undefined)
	{
		DstCell.textContent = SrcCell.textContent;
		DstItem.Ch = SrcItem.Ch;
		if ((DstX === this.x) && (DstY === this.y))
		{
			this.DrawCursor();
		}
	}
} // .function Console.CopyAttr

/*
[DESC] Устанавливает новое значение прозрачности для ячейки
*/
Console.prototype.SetCellOpacity = function (X, Y, NewOpacity)
{
	var	x = this.x1 + X,
		y = this.y1 + Y,
		Cell = $(this.GetCellId(x, y));
	Cell.style.opacity = NewOpacity;
} // .function Console.SetCellOpacity

/*
[DESC] Возвращает значение прозрачности для ячейки
*/
Console.prototype.GetCellOpacity = function (X, Y)
{
	var	x = this.x1 + X,
		y = this.y1 + Y,
		Cell = $(this.GetCellId(x, y)),
		result = Cell.style.opacity;
	if (result === '')
	{
		result = 1;
	}
	return result;
} // .function Console.GetCellOpacity

/*----------------------------------------------------------------------------*/


/*-----------------  РАБОТА С ПРЯМОУГОЛЬНЫМИ ОБЛАСТЯМИ  ----------------------*/

/*
[DESC] Обрезает прямоугольник до рамок окна консоли. Результат истинен, если хоть одна ячейка находится в рамках консоли
[RET] TClipRect
*/
Console.prototype.ClipRect = function (x1, y1, x2, y2)
{
	var result = {result: false};
	if (((x2 - x1 + 1) > 0) && ((y2 - y1 + 1) > 0) && (Math.abs(x1) <= (this.Width - 1)) && (Math.abs(x2) >= 0) && (Math.abs(y1) <= (this.Height - 1)) && (Math.abs(y2) >= 0))
	{
		result =
		{
			result: true,
			x1: Math.max(x1, 0),
			x2: Math.min(x2, this.Width - 1),
			y1: Math.max(y1, 0),
			y2: Math.min(y2, this.Height - 1)
		}
	}
	return result;
} // .function Console.ClipRect

/*
[DESC] Устанавливает новые границы текущего окна в консоли. Курсор устанавливается в верхний левый угол
[CONVENTION TClipRect] x1..y2
*/
Console.prototype.Window = function (x1, y1, x2, y2)
{
	var Rect = this.ClipRect(x1, y1, x2, y2);
	if (Rect.result)
	{
		if (this.Cursor)
		{
			this.EraseCursor();
		}
		this.x1 = Rect.x1;
		this.x2 = Rect.x2;
		this.y1 = Rect.y1;
		this.y2 = Rect.y2;
		this.WndWidth = Rect.x2 - Rect.x1 + 1;
		this.WndHeight = Rect.y2 -Rect. y1 + 1;
		this.x = 0;
		this.y = 0;
		this.DrawCursor();
	}
} // .function Console.Window

/*
[DESC] Заполняет указанную прямоугольную область консоли однородной заливкой
[CONVENTION TClipRect] x1..y2
*/
Console.prototype.FillRect = function (x1, y1, x2, y2, Ch)
{
	var	Rect = this.ClipRect(x1, y1, x2, y2),
		i = 0,
		y = 0;
	if (Rect.result)
	{
		x1-=this.x1;
		y1-=this.y1;
		x2-=this.x2;
		y2-=this.y2;
		for (y = Rect.y1; y <= Rect.y2; y++)
		{
			for (i = Rect.x1; i <= Rect.x2; i++)
			{
				this.WriteChar(i, y, Ch);
			}
		}
	}
} // .function Console.FillRect

// [DESC] Очищает содержимое текущего окна, курсор перемещается в точку [0,0]
Console.prototype.Clear = function ()
{
	this.FillRect(this.x1, this.y1, this.x2, this.y2, ' ');
	this.GotoXY(0, 0);
} // .function Console.Clear

/*
[DESC] Считывает атрибуты прямоугольной матрицы ячеек в массив
[CONVENTION TClipRect] x1..y2
[RET] ARRAY OF TAttr
*/
Console.prototype.ReadImage = function (x1, y1, x2, y2)
{
	var	Rect = this.ClipRect(x1, y1, x2, y2),
		i = 0,
		y = 0,
		Item = null,
		result = [];
	if (Rect.result)
	{
		for (y = Rect.y1; y <= Rect.y2; y++)
		{
			for (i = Rect.x1; i <= Rect.x2; i++)
			{
				Item = this.Rows[y][i];
				result.push
				({
					Col: Item.Col,
					Back: Item.Back,
					Ch: Item.Ch
				});
			}
		}
	}
	return result;
} // .function Console.ReadImage

/*
[DESC] Рисует картинку в указанной прямоугольной матрице
[CONVENTION TClipRect] x1..y2
[ARG Img] ARRAY OF TAttr
*/
Console.prototype.WriteImage = function (x1, y1, x2, y2, Img)
{
	var	Rect = this.ClipRect(x1, y1, x2, y2),
		OrigW = x2 - x1 + 1,
		OrigH = y2 - y1 + 1,
		RectW = 0,
		RectH = 0,
		DiffW = 0,
		Pos = 0,
		Len = Img.length,
		x = 0,
		y = 0;
	if (Rect.result)
	{
		RectW = Rect.x2 - Rect.x1 + 1;
		RectH = Rect.y2 - Rect.y1 + 1;
		DiffW = OrigW - RectW;
		Pos =(Rect.y1 - y1) * OrigW + (Rect.x1 - x1);
		for (y = Rect.y1; (y <= Rect.y2) && (Pos < Len); y++)
		{
			for (x = Rect.x1; (x <= Rect.x2) && (Pos < Len); x++)
			{
				this.WriteAttr(x, y, Img[Pos]);
				Pos++;
			}
			Pos = Pos + DiffW;
		}
	}
} // .function Console.WriteImage

/*----------------------------------------------------------------------------*/


/*------------------  ОПЕРАЦИИ НАД ЦЕПОЧКАМИ СИМВОЛОВ  -----------------------*/

// [DESC] Осуществляет скролинг содержимого текущего окна на несколько строк вверх. Позиция курсора не меняется
Console.prototype.ScrollUp = function (NumLines)
{
	var	x = 0,
		y = 0,
		MaxX = this.WndWidth - 1,
		MaxY = this.WndHeight - 1;
	if (NumLines >= this.WndHeight)
	{
		this.FillRect(0, 0, MaxX, MaxY, ' ');
	}
	else
	{
		for (y = NumLines; y <= MaxY; y++)
		{
			for (x =0; x <= MaxX; x++)
			{
				this.CopyChar(x, y, x, y - NumLines);
			}
		}
	}
	this.FillRect(0, MaxY - NumLines + 1, MaxX, MaxY - NumLines + 1, ' ');
} // .function Console.ScrollUp

/*
[DESC] Сдвигает цепочку символов вправо или влево, считая всё текущее окно линейным массивом ячеек. Разрешается выход цепочки за границы текущего окна после сдвига (лишнее не рисуется). Курсор не меняет положения
[ARG X, Y]	Координаты первой ячейки цепочки
[ARG Len]	Длина цепочки (кол-во ячеек). Допускается <= 0 (сдвига не будет)
[ARG By]	Сдвиг на .. ячеек. Если число положительное, то вправо, иначе влево
*/
Console.prototype.ShiftChars = function (X, Y, Len, By)
{
	var	Start = Y * this.WndWidth + X,				// Линейная координата начала цепочки. Цепочка может начинаться и с правого конца
		End = Start + Len - 1,						// Линейная координата первой ячейки-приёмника
		Max = this.WndWidth * this.WndHeight - 1,	// Максимальная линейная координата правого конца цепочки ячеек (определяется размерами окна)
		SrcX = 0,									// Координаты текущей ячейки-источника для копирования
		SrcY = 0,									// ...
		DstX = 0,									// Координаты текущей ячейки-приёмника для копирования
		DstY = 0,									// ...
		Move = -Sign(By);							// Шаг сдвига координат (-1 или +1). Используется в безопасном цикле копирования
	// Если указано смещение и цепочка ненулевой длинны
	if ((By !== 0) && (Len > 0))
	{
		// Если смещение вправо, то началом цепочки является её конец
		if (By > 0)
		{
			Start = End;
		}
		// Линейная координата первой ячейки-приёмника определяется как координата начала цепочки + смещение
		End = Start + By;
		// Если сдвиг влево/вправо и часть символов выходят за пределы окна, то они обрезаются. Соответственно корректируем длину цепочки и линейную координату её начала
		if (End < 0)
		{
			Len = Len + End;
			Start = Start - End;
			End = 0;
		}
		else if (End > Max)
		{
			Len = Len + Max - End;
			Start = Start + Max - End;
			End = Max;
		}
		// Получаем обычные координаты первой ячейки-источника и ячейки-приёмника
		SrcX = Start % this.WndWidth;
		SrcY = DIV(Start, this.WndWidth);
		DstX = End % this.WndWidth;
		DstY = DIV(End, this.WndWidth);
		if (Len > 0)
		{
			// Копируем в цикле атрибуты из ячеек-источников в ячейки приёмники
			for (i = 0; i < Len; i++)
			{
				this.CopyChar(SrcX, SrcY, DstX, DstY);
				SrcX = SrcX + Move;
				if (SrcX < 0)
				{
					SrcX = this.WndWidth - 1;
					SrcY--;
				}
				else if (SrcX === this.WndWidth)
				{
					SrcX = 0;
					SrcY++;
				}
				DstX = DstX + Move;
				if (DstX < 0)
				{
					DstX = this.WndWidth - 1;
					DstY--;
				}
				else if (DstX === this.WndWidth)
				{
					DstX = 0;
					DstY++;
				}
			}
		}
	}
} // .function Console.ShiftChars

// [DESC] Выводит указанную строку в текущее окно. Понимает символ #10 как перевод строки, трактует #9 (TAB) как пробел. Осуществляет автоматическую прокрутку при необходимости. После вывода курсор устанавливается на следующей за последним символом позиции
Console.prototype.Write = function (Str)
{
	var	OrigLen = this.y * this.WndWidth + this.x,	// Размер оригинального текста в окне до позиции ввода
		NewOrigLen = 0,								// Размер оригинального текста в окне после вывода текста в виртуальный буфер
		BufLen = OrigLen,							// Размер виртуального буфера, куда происходит запись
		WndLen = this.WndWidth * this.WndHeight,	// Размер окна
		Txt = Str.split(/\r?\n/),					// Выводимый текст, разбитый на строки
		TxtLen = 0,									// Виртуальный размер текста
		EndInd = Txt.length - 1,					// Конечный индекс в массиве строк текста
		CaretLen = {'-1': 0},						// Массив размеров символов переводов строк (перевод строки съедает всё место до конца строки консоли)
		StrInd = 0,									// Индекс строки текста, с которой будет осуществлён вывод
		StrPos = 0,									// Позиция в строке текста, с которой будет осуществлён вывод
		StrLen = 0,									// Размер финальной выводимой строки
		x = this.x,									// Координаты вывода текста
		y = this.y,									// ...
		i = 0;
	// Рассчитываем размер виртуального буфера после вывода текста, а также размеры концов строк
	for (i = 0; i <= EndInd; i++)
	{
		BufLen = BufLen + Txt[i].length;
		CaretLen[i] = this.WndWidth - BufLen % this.WndWidth
		BufLen = BufLen + CaretLen[i];
	}
	TxtLen = BufLen - OrigLen;
	if (BufLen > WndLen)
	{
		// Часть оригинального текста сохранится при прокрутке
		if (TxtLen < WndLen)
		{
			Str = Txt.join('\n');
			NewOrigLen = WndLen - TxtLen;
			this.ShiftChars(0, 0, OrigLen, NewOrigLen - OrigLen);
			x = NewOrigLen % this.WndWidth;
			y = DIV(NewOrigLen, this.WndWidth);
			this.FillRect(0, this.WndHeight - 1, this.WndWidth - 1, this.WndHeight - 1, ' ');
		}
		// Весь экран будет переписан новым текстом
		else
		{
			// Вычисляем индекс начальной строки текста для вывода и позицию в ней
			i = 0;
			for (StrInd = EndInd; i < WndLen; StrInd--)
			{
				i = i + Txt[StrInd].length + CaretLen[StrInd];
			}
			StrInd++;
			if (i > WndLen)
			{
				StrPos = i - WndLen;
				Txt[StrInd] = Txt[StrInd].slice(StrPos);
			}
			else
			{
				StrPos = 0;
			}
			Str = Txt.slice(StrInd).join("\n");
			x = 0;
			y = 0;
		}
		if (CaretLen[EndInd] === this.WndWidth)
		{

		}
		this.FillRect(0, this.WndHeight - 1, CaretLen[EndInd] - 1, this.WndHeight - 1, ' ');
	}
	else
	{
		Str = Txt.join('\n');
	}
	// Выводим текст
	StrLen = Str.length;
	for (i = 0; i < StrLen; i++)
	{
		if (Str[i] === "\n")
		{
			x = 0;
			y++;
		}
		else
		{
			if (Str.charCodeAt[i] < 32)
			{
				this.WriteChar(x, y, ' ');
			}
			else
			{
				this.WriteChar(x, y, Str[i]);
			}
			x++;
			if (x === this.WndWidth)
			{
				x = 0;
				y++;
			}
		}
	}
	this.GotoXY(x, y);
} // .function Console.Write

/*
[DESC] Читает цепочку символов текущего окна, длиной Len и с началом в указанной позиции
[ARG Len] Длина считываемой цепочки. Если она превышает максимальное значение для текущего окна, то возвращается обрезанный вариант
*/
Console.prototype.ReadScreen = function (X, Y, Len)
{
	var	result = [],							// Массив прочитанных символов
		End = this.WndWidth * this.WndHeight,	// Конец линейного массива ячеек окна
		Pos = Y * this.WndWidth + X,			// Текущая линейная позиция в массиве ячеек окна
		i = 0;
	// Если цепочка символов слишком большая, обрезаем её
	if (((Pos + Len)) > End)
	{
		Len = End - Pos;
	}
	// Если длина цепочки положительна
	if (Len > 0)
	{
		for (i = 0; i < Len; i++)
		{
			result.push(this.CharAt(X, Y));
			X++;
			if (X === this.WndWidth)
			{
				X = 0;
				Y++;
			}
		}
	}
	return result.join('');
} // .function Console.ReadScreen

/*----------------------------------------------------------------------------*/


/*------------------------  РАБОТА С КЛАВИАТУРОЙ  ----------------------------*/

/*
[DESC] Возвращает true, если клавиатурное событие - удерживание клавиши
*/
Console.prototype.IsKeyHoldStage = function (Stage)
{
	return ((Stage === this.KEYPRESS) && (this.LastStage === this.KEYPRESS));
} // .function Console.IsKeyHoldStage

/*
[DESC] Обработчик событий клавиатуры нижнего уровня. Получает коды событий 1-3, самостоятельно генерирует >3, если есть пользовательский ввод
[CONVENTION EventHandler]
*/
Console.prototype._DefEventHandler = function (e, Stage)
{
	var result;
	this.Handler = this.Handlers[0];
	result = this.Handler(e, Stage);
	e.Txt = this.Input.value;
	if (e.Txt.length > 0)
	{
		this.Input.value = '';
		this.Handler(e, this.INPUT);
	}
	this.LastStage = Stage;
	return result;
} // .function Console._DefEventHandler

/*
[DESC] Регистрирует новый обработчик событий консоли. Возвращает его индекс
[ARG Handler: TEventHandler] Регистрируемый обработчик событий
*/
Console.prototype.AddEventHandler = function (Handler)
{
	return this.Handlers.push(Handler);
} // .function Console.AddEventHandler

// [DESC] Удаляет обработчик событий с указанным индексом
Console.prototype.RemoveEventHandler = function (Ind)
{
	if ((this.Handlers.length - 1) === Ind)
	{
		this.Handlers.pop();
	}
	else
	{
		this.Handlers = this.Handlers.splice(Ind, 1);
	}
} // .function Console.RemoveEventHandler

/*
[DESC] Отменяет последний вызов асинхронной функции работы с консолью
*/
Console.prototype.CancelLastFunc = function ()
{
	if (this.Handlers.length > 0)
	{
		this.RemoveEventHandler(0);
		this.HandlerStopped = true;
	}
} // .function Console.CancelLastFunc

/*
[DESC] Ждёт, пока пользователь нажмёт клавишу и возвращает её код. Реакция по молчанию на событие KEYDOWN подавляется
[CONVENTION CallBack] CallBack
[CONVENTION WaitUntilStopped]
*/
Console.prototype.ReadKey = function (CallBack)
{
	Assert(this.HandlerStopped);
	this.HandlerStopped = false;
	this.CallBack = CallBack;
	this.Handlers.unshift(this._ReadKey);
	this.Input.value = '';
} // .function Console.ReadKey

/*
[DESC] Обработчик событий для ReadKey
[CONVENTION EventHandler]
*/
Console.prototype._ReadKey = function (e, Stage)
{
	if (Stage === this.KEYDOWN)
	{
		this.HandlerStopped = true;
		this.Handlers.shift();
		this.CallBack(e.keyCode);
	}
	return false;
} // .function Console._ReadKey

/*
[DESC] Ждёт, пока пользователь введёт один символ или вставит текст из буфера обмена. Возвращает результат ввода
[CONVENTION CallBack] CallBack
[CONVENTION WaitUntilStopped]
*/
Console.prototype.ReadInput = function (CallBack)
{
	Assert(this.HandlerStopped);
	this.HandlerStopped = false;
	this.CallBack = CallBack;
	this.Handlers.unshift(this._ReadInput);
	this.Input.value = '';
} // .function Console.ReadInput

/*
[DESC] Обработчик событий для ReadInput
[CONVENTION EventHandler]
*/
Console.prototype._ReadInput = function (e, Stage)
{
	if (Stage === this.INPUT)
	{
		this.HandlerStopped = true;
		this.Handlers.shift();
		this.CallBack(e.Txt);
	}
} // .function Console._ReadInput

/*
[DESC] Очищает буфер мульти-ввода
*/
Console.prototype.FlushInputBuffer = function ()
{
	this.InpBuf = '';
} // .function Console.FlushInputBuffer

/*
[DESC] "Читает строку из консоли. Enter служит подтверждением ввода.
Поддерживается редактирование через BACKSPACE, DELETE (один символ), CTRL + BACKSPACE, CTRL + DELETE (все символы в одну сторону), навигация через HOME, END (в пределах строки), CTRL + HOME, CTRL + END (в пределах всего введённого текста), ВНИЗ, ВВЕРХ, ВЛЕВО, ВПРАВО, вставка данных из буфера, переключение режимов вставки/замены по INSERT.
Если произошло достижение конца окна, то оно прокручивается на строку вверх, а самая верхняя строка окна теряется для редактирования, но остаётся в буфере. Поддерживает мульти-ввод.
По окончании ввода, если есть после текста место для курсора (максимальный размер буфера не исчерпан), то курсор устанавливается за следующим символом после всего введённого текста и ему возвращается оригинальная видимость, иначе его позиция и видимость не меняются. Если курсор заходит за рамки ввода, то он исчезает, что позволяет реализовать эргономичные поля ввода фиксированного размера."
[CONVENTION CallBack] CallBack
[CONVENTION WaitUntilStopped]
[ARG MinLen, MaxLen]	Минимальный и максимальный размер допустимого ввода. -1 - нет ограничений
[ARG BufLen]			Размер начального буфера для редактирования. Используется, если есть нужно позволить редактировать часть текста, уже имеющегося в окне как будто пользователь его ввёл сам
[ARG BufOfs]			Смещение в буфере (см. аргумент BufLen) до текущей позиции. Используется, если нужно начать редактирование не с начала буфера
*/
Console.prototype.ReadEx = function (CallBack, MinLen, MaxLen, BufLen, BufOfs)
{
	var	InpX = -1,
		InpY = -1,
		Pos = -1;
	Assert(this.HandlerStopped);
	Pos = this.y * this.WndWidth + this.x;
	this.HandlerStopped = false;
	this.CallBack = CallBack;
	this.Handlers.unshift(this._ReadEx);
	this.Input.value = '';
	this.LastStage = this.KEYUP;
	this.MinLen = MinLen;
	this.MaxLen = MaxLen;
	Pos = Pos - BufOfs;
	this.InpX = Pos % this.WndWidth;
	this.InpY = DIV(Pos, this.WndWidth);
	this.Buf = this.ReadScreen(this.InpX, this.InpY, BufLen);
	this.BufPos = BufOfs;
	this.SavedCursor = this.Cursor;
	this._HandleMultiInput();
} // .function Console.ReadEx

// [DESC] Функция-заглушка, используемая _HandleMultiInput
Console.prototype._CallBackTrap = function ()
{
	this.Handler = null;
} // .function Console._CallBackTrap

// [DESC] Реализация мульти-ввода, то есть обработки вставленного из буфера обмена текста по строкам, как отдельных операций ввода. Один вызов ReadEx - одна строка
Console.prototype._HandleMultiInput = function ()
{
	var	EolPos = 0,
		Txt = '',
		CallBack = this.CallBack;
	this.CallBack = this._CallBackTrap;
	this.Handler = this._CallBackTrap;
	while ((this.InpBuf.length > 0) && (this.Handler !== null))
	{
		EolPos = this.InpBuf.indexOf('\n');
		if (EolPos === -1)
		{
			var Txt = this.InpBuf;
			this.InpBuf = '';
		}
		else
		{
			var Txt = this.InpBuf.slice(0, EolPos);
			this.InpBuf = this.InpBuf.slice(EolPos + 1);
		}
		this._ReadEx({keyCode: 0, Txt: Txt}, this.INPUT);
		if (EolPos !== -1)
		{
			this._ReadEx({keyCode: this.ENTER}, 1);
		}
	}
	if (this.Handler === null)
	{
		this.Handler = CallBack;
		this.Handler(this.Buf);
	}
	else
	{
		this.CallBack = CallBack;
	}
} // .function Console._HandleMultiInput

Console.prototype._HandleKeyDelete = function (e)
{
	var	OrigLen = this.y * this.WndWidth + this.x,
		LastCharPos = OrigLen + (this.Buf.length - this.BufPos) - 1,
		x = LastCharPos % this.WndWidth,
		y = DIV(LastCharPos, this.WndWidth),
		NumDel = this.Buf.length - this.BufPos,
		ChainX = this.x,
		ChainY = this.y.
		i = 0;
	if (e.ctrlKey)
	{
		this.Buf = this.Buf.slice(0, this.BufPos);
		for (i = 0; i < NumDel; i++)
		{
			this.WriteChar(x, y, ' ');
			x--;
			if (x < 0)
			{
				x = this.WndWidth - 1;
				y--;
			}
		}
	}
	else
	{
		ChainX = ChainX + 1;
		if (ChainX === this.WndWidth)
		{
			ChainX = 0;
			ChainY++;
		}
		this.ShiftChars(ChainX, ChainY, NumDel - 1, -1);
		this.WriteChar(x, y, ' ');
		this.Buf = this.Buf.slice(0, this.BufPos) + this.Buf.slice(this.BufPos + 1);
	}
} // .function Console._HandleKeyDelete

// [DESC] Обработчик клавиши ENTER для функции ReadEx
Console.prototype._HandleKeyEnter = function ()
{
	var	x = this.x,
		y = this.y,
		OrigLen = x * this.WndWidth + y,
		Pos = 0;
	// Если есть место для установки курсора в конец текста
	if ((this.MaxLen === -1) || (this.Buf.length < this.MaxLen))
	{
		Pos = x + (this.Buf.length - this.BufPos);
		x = Pos % this.WndWidth;
		y = y + DIV(Pos, this.WndWidth);
		this.GotoXY(x, y);
		this.SetCursorVisibility(this.SavedCursor);
	}
	this.HandlerStopped = true;
	this.Handlers.shift();
	this.CallBack(this.Buf);
} // .function Console._HandleKeyEnter

// [DESC] Обработчик клавиши BACKSPACE для функции ReadEx
Console.prototype._HandleKeyBackspace = function (e)
{
	var	OrigLen = this.y * this.WndWidth + this.x,
		LastCharPos = OrigLen + (this.Buf.length - this.BufPos) - 1,
		x = LastCharPos % this.WndWidth,
		y = DIV(LastCharPos, this.WndWidth),
		Pos = 0,
		NumDel = 0,
		NumSaved = 0,
		i = 0;
	if (e.ctrlKey)
	{
		NumDel = Math.min(OrigLen, this.BufPos);
	}
	else
	{
		NumDel = 1;
	}
	this.ShiftChars(this.x, this.y, this.Buf.length - this.BufPos, -NumDel);
	if (e.ctrlKey)
	{
		for (i = 0; i < NumDel; i++)
		{
			this.WriteChar(x, y, ' ');
			x--;
			if (x < 0)
			{
				x = this.WndWidth - 1;
				y--;
			}
		}
		NumSaved = this.BufPos - NumDel;
		this.Buf = this.Buf.slice(0, NumSaved) + this.Buf.slice(this.BufPos);
		this.BufPos = NumSaved;
		Pos = OrigLen - NumDel;
		this.GotoXY(Pos % this.WndWidth, DIV(Pos, this.WndWidth));
	}
	else
	{
		this.WriteChar(x, y, ' ');
		this.Buf = this.Buf.slice(0, this.BufPos - 1) + this.Buf.slice(this.BufPos);
		this.BufPos--;
		if (this.x === 0)
		{
			this.GotoXY(this.WndWidth - 1, this.y - 1);
		}
		else
		{
			this.GotoXY(this.x - 1, this.y);
		}
	}
	this.SetCursorVisibility(this.SavedCursor);
} // .function Console._HandleKeyBackspace

// [DESC] Обработчик клавиши LEFT для функции ReadEx
Console.prototype._HandleKeyLeft = function ()
{
	this.BufPos--;
	if (this.x === 0)
	{
		this.GotoXY(this.WndWidth - 1, this.y - 1);
	}
	else
	{
		this.GotoXY(this.x - 1, this.y);
	}
	this.SetCursorVisibility(this.SavedCursor);
} // .function Console._HandleKeyLeft

// [DESC] Обработчик клавиши RIGHT для функции ReadEx
Console.prototype._HandleKeyRight = function ()
{
	this.BufPos++;
	if (this.x === (this.WndWidth - 1))
	{
		this.GotoXY(0, this.y + 1);
	}
	else
	{
		this.GotoXY(this.x + 1, this.y);
	}
	if ((this.MaxLen !== -1) && (this.BufPos === this.MaxLen))
	{
		this.SetCursorVisibility(false);
	}
} // .function Console._HandleKeyRight

// [DESC] Обработчик клавиши UP для функции ReadEx
Console.prototype._HandleKeyUp = function ()
{
	this.BufPos = this.BufPos - this.WndWidth;
	this.GotoXY(this.x, this.y - 1);
	if ((this.MaxLen !== -1) && (this.BufPos === this.MaxLen))
	{
		this.SetCursorVisibility(false);
	}
} // .function Console._HandleKeyUp

// [DESC] Обработчик клавиши DOWN для функции ReadEx
Console.prototype._HandleKeyDown = function ()
{
	this.BufPos = this.BufPos + this.WndWidth;
	this.GotoXY(this.x, this.y + 1);
	this.SetCursorVisibility(this.SavedCursor);
} // .function Console._HandleKeyDown

// [DESC] Обработчик клавиши HOME для функции ReadEx
Console.prototype._HandleKeyHome = function (e)
{
	var	OrigLen = this.y * this.WndWidth + this.x,
		StartPos = OrigLen - this.BufPos,
		MoveBy = Math.min(this.BufPos, this.x);
	if (e.ctrlKey)
	{
		if (StartPos < 0)
		{
			this.BufPos = -StartPos;
			StartPos = 0;
		}
		else
		{
			this.BufPos = 0;
		}
		this.GotoXY(StartPos % this.WndWidth, DIV(StartPos, this.WndWidth));
	}
	else if (this.x > 0)
	{
		this.BufPos = this.BufPos - MoveBy;
		this.GotoXY(this.x - MoveBy, this.y);
	}
	this.SetCursorVisibility(this.SavedCursor);
} // .function Console._HandleKeyHome

// [DESC] Обработчик клавиши END для функции ReadEx
Console.prototype._HandleKeyEnd = function (e)
{
	var	NumCharsRight = this.Buf.length - this.BufPos,
		OrigLen = this.y * this.WndWidth + this.x,
		EndPos = 0,
		MoveBy = 0;
	if (e.ctrlKey)
	{
		EndPos = OrigLen + NumCharsRight;
		this.BufPos = this.Buf.length;
		this.GotoXY(EndPos % this.WndWidth, DIV(EndPos, this.WndWidth));
	}
	else
	{
		MoveBy = Math.min(this.WndWidth - 1 - this.x, NumCharsRight);
		EndPos = OrigLen + MoveBy;
		this.BufPos = this.BufPos + MoveBy;
		this.GotoXY(EndPos % this.WndWidth, DIV(EndPos, this.WndWidth));
	}
	if ((this.MaxLen !== -1) && (this.BufPos === this.MaxLen))
	{
		this.SetCursorVisibility(false);
	}
} // .function Console._HandleKeyEnd

// [DESC] Обработчик специальных клавиш для функции ReadEx
Console.prototype._HandleSpecial = function (e)
{
	var	c = e.keyCode,
		x = this.x,
		y = this.y;
	// BACKSPACE
	if ((c === this.BACKSPACE) && (this.BufPos > 0) &&((x > 0) || (y > 0)))
	{
		this._HandleKeyBackspace(e);
	}
	// ENTER
	else if ((c === this.ENTER) && ((this.MinLen === -1) || (this.Buf.length >= this.MinLen)))
	{
		this._HandleKeyEnter();
	}
	// END
	else if ((c === this.END) && (this.BufPos < this.Buf.length))
	{
		this._HandleKeyEnd(e);
	}
	// HOME
	else if ((c === this.HOME) && (this.BufPos > 0))
	{
		this._HandleKeyHome(e);
	}
	// LEFT
	else if ((c === this.LEFT) && (this.BufPos > 0) &&((x > 0) || (y > 0)))
	{
		this._HandleKeyLeft();
	}
	// UP
	else if ((c === this.UP) && ((this.BufPos >= this.WndWidth) && (this.y > 0)))
	{
		this._HandleKeyUp();
	}
	// RIGHT
	else if ((c === this.RIGHT) && (this.BufPos < this.Buf.length))
	{
		this._HandleKeyRight();
	}
	// DOWN
	else if ((c === this.DOWN) && ((this.BufPos + this.WndWidth) <= this.Buf.length))
	{
		this._HandleKeyDown();
	}
	// INSERT
	else if ((c === this.INSERT) && !e.ctrlKey && !e.shiftKey && !e.altKey)
	{
		this.ModeReplace = !this.ModeReplace;
		this.DrawCursor();
	}
	// DELETE
	else if ((c === this.DELETE) && (this.BufPos < this.Buf.length))
	{
		this._HandleKeyDelete(e);
	}
} // .function Console._HandleSpecial

// [DESC] Обработчик события ввода для функции ReadEx
Console.prototype._HandleInput = function (Txt)
{
	var	BufHasFreeSpace = (this.Buf.length !== this.MaxLen),
		BufOverflow = ((this.MaxLen !== -1) && BufHasFreeSpace && ((this.Buf.length + Txt.length) > this.MaxLen)),
		OldPos = this.y * this.WndWidth + this.x,
		Pos = 0,
		LeftBufPart = '',
		RightBufPart = '',
		OutTxt = '';
	// Сперва обрабатываем мульти-ввод
	if (Txt.indexOf('\n') !== -1)
	{
		this.InpBuf = Txt.split(/\r?\n/).join('\n');
		this._HandleMultiInput();
	}
	else
	{
		// Если текущая позиция - конец буфера, то вид операции только один: вставка в конец
		if (this.BufPos === this.Buf.length)
		{
			if (BufOverflow)
			{
				Txt = Txt.slice(0, this.MaxLen - this.Buf.length);
			}
			if (BufHasFreeSpace)
			{
				this.Buf+=Txt;
				this.BufPos = this.BufPos + Txt.length;
				this.Write(Txt);
			}
		}
		// Замена
		else if (this.ModeReplace)
		{
			if ((this.MaxLen !== -1) && ((this.BufPos + Txt.length) > this.MaxLen))
			{
				Txt = Txt.slice(0, this.MaxLen - this.BufPos);
			}
			this.Buf = this.Buf.slice(0, this.BufPos) + Txt + this.Buf.slice(this.BufPos + Txt.length);
			this.BufPos = this.BufPos + Txt.length;
			this.Write(Txt);
		}
		// Вставка в середину
		else if (BufHasFreeSpace)
		{
			if (BufOverflow)
			{
				Txt = Txt.slice(0, this.MaxLen - this.Buf.length);
			}
			LeftBufPart = this.Buf.slice(0, this.BufPos);
			RightBufPart = this.Buf.slice(this.BufPos);
			OutTxt = Txt + RightBufPart;
			this.Write(OutTxt);
			this.Buf = LeftBufPart + OutTxt;
			this.BufPos = this.BufPos + Txt.length;
			Pos = this.y * this.WndWidth + this.x - RightBufPart.length;
			if (Pos < 0)
			{
				this.BufPos = this.BufPos - Pos;
			}
			Pos = Math.max(0, Pos);
			this.GotoXY(Pos % this.WndWidth, DIV(Pos, this.WndWidth));
		}
		if ((this.MaxLen !== -1) && (this.BufPos === this.MaxLen))
		{
			this.SetCursorVisibility(false);
		}
	}
} // .function Console._HandleInput

// [DESC] Обработчик событий верхнего уровня для функции ReadEx
Console.prototype._ReadEx = function (e, Stage)
{
	var result;
	if (this.SpecialKeys[e.keyCode] !== undefined)
	{
		if ((Stage === this.KEYDOWN) || this.IsKeyHoldStage(Stage))
		{
			result = this._HandleSpecial(e);
		}
	}
	else if (Core.InRange(e.keyCode, 1, 31) && (this.SystemKeys[e.keyCode] === undefined))
	{
		result = false;
	}
	else if (Stage === this.INPUT)
	{
		result = this._HandleInput(e.Txt);
	}
	return result;
} // .function Console._ReadEx
