<commit_before>/* jslint node: true */
'use strict';

const View			= require('./view.js').View;
const strUtil		= require('./string_util.js');
const ansi			= require('./ansi_term.js');
const colorCodes	= require('./color_codes.js');
const wordWrapText	= require('./word_wrap.js').wordWrapText;

const assert		= require('assert');
const _				= require('lodash');

//	:TODO: Determine CTRL-* keys for various things
	//	See http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
	//	http://wiki.synchro.net/howto:editor:slyedit#edit_mode
	//	http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_win.html

	/* Mystic
	 [^B]  Reformat Paragraph            [^O]  Show this help file
       [^I]  Insert tab space              [^Q]  Enter quote mode
       [^K]  Cut current line of text      [^V]  Toggle insert/overwrite
       [^U]  Paste previously cut text     [^Y]  Delete current line


                            BASIC MOVEMENT COMMANDS

                  UP/^E       LEFT/^S      PGUP/^R      HOME/^F
                DOWN/^X      RIGHT/^D      PGDN/^C       END/^G
*/

//
//	Some other interesting implementations, resources, etc.
//
//	Editors - BBS
//	*	https://github.com/M-griffin/Enthral/blob/master/src/msg_fse.cpp
//
//
//	Editors - Other
//	*	http://joe-editor.sourceforge.net/
//	* 	http://www.jbox.dk/downloads/edit.c
//	*	https://github.com/dominictarr/hipster
//
//	Implementations - Word Wrap
//	*	https://github.com/protomouse/synchronet/blob/93b01c55b3102ebc3c4f4793c3a45b8c13d0dc2a/src/sbbs3/wordwrap.c
//
//	Misc notes
//	* https://github.com/dominictarr/hipster/issues/15 (Deleting lines/etc.)
//
//	Blessed
//		insertLine: CSR(top, bottom) + CUP(y, 0) + IL(1) + CSR(0, height)
//		deleteLine: CSR(top, bottom) + CUP(y, 0) + DL(1) + CSR(0, height)
//	Quick Ansi -- update only what was changed:
//	https://github.com/dominictarr/quickansi

//
//	To-Do
//
//	* Index pos % for emit scroll events
//	* Some of this shoudl be async'd where there is lots of processing (e.g. word wrap)
//	* Fix backspace when col=0 (e.g. bs to prev line)
//	* Add back word delete
//	*


var SPECIAL_KEY_MAP_DEFAULT = {
	'line feed'		: [ 'return' ],
	exit			: [ 'esc' ],
	backspace		: [ 'backspace' ],
	delete			: [ 'del' ],
	tab				: [ 'tab' ],
	up				: [ 'up arrow' ],
	down			: [ 'down arrow' ],
	end				: [ 'end' ],
	home			: [ 'home' ],
	left			: [ 'left arrow' ],
	right			: [ 'right arrow' ],
	'delete line'	: [ 'ctrl + y' ],
	'page up'		: [ 'page up' ],
	'page down'		: [ 'page down' ],
	insert			: [ 'insert', 'ctrl + v' ],
};

exports.MultiLineEditTextView	= MultiLineEditTextView;

function MultiLineEditTextView(options) {
	if(!_.isBoolean(options.acceptsFocus)) {
		options.acceptsFocus = true;
	}

	if(!_.isBoolean(this.acceptsInput)) {
		options.acceptsInput = true;
	}

	if(!_.isObject(options.specialKeyMap)) {
		options.specialKeyMap = SPECIAL_KEY_MAP_DEFAULT;
	}

	View.call(this, options);

	var self = this;

	//
	//	ANSI seems to want tabs to default to 8 characters. See the following:
	//	* http://www.ansi-bbs.org/ansi-bbs2/control_chars/
	//	* http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
	//
	//	This seems overkill though, so let's default to 4 :)
	//	:TODO: what shoudl this really be? Maybe 8 is OK
	//
	this.tabWidth			= _.isNumber(options.tabWidth) ? options.tabWidth : 4;

	this.textLines			= [ ];
	this.topVisibleIndex	= 0;
	this.mode				= options.mode || 'edit';	//	edit | preview | read-only

	if ('preview' === this.mode) {
		this.autoScroll = options.autoScroll || true;
		this.tabSwitchesView = true;
	} else {
		this.autoScroll = options.autoScroll || false;
		this.tabSwitchesView = options.tabSwitchesView || false;
	}
	//
	//	cursorPos represents zero-based row, col positions
	//	within the editor itself
	//
	this.cursorPos			= { col : 0, row : 0 };

	this.getSGRFor = function(sgrFor) {
		return {
			text : self.getSGR(),
		}[sgrFor] || self.getSGR();
	};

	this.isEditMode = function() {
		return 'edit' === self.mode;
	};

	this.isPreviewMode = function() {
		return 'preview' === self.mode;
	};

	//	:TODO: Most of the calls to this could be avoided via incrementRow(), decrementRow() that keeps track or such
	this.getTextLinesIndex = function(row) {
		if(!_.isNumber(row)) {
			row = self.cursorPos.row;
		}
		var index = self.topVisibleIndex + row;
		return index;
	};

	this.getRemainingLinesBelowRow = function(row) {
		if(!_.isNumber(row)) {
			row = self.cursorPos.row;
		}
		return self.textLines.length - (self.topVisibleIndex + row) - 1;
	};

	this.getNextEndOfLineIndex = function(startIndex) {
		for(var i = startIndex; i < self.textLines.length; i++) {
			if(self.textLines[i].eol) {
				return i;
			}
		}
		return self.textLines.length;
	};

	this.toggleTextCursor = function(action) {
		self.client.term.rawWrite(`${self.getSGRFor('text')}${'hide' === action ? ansi.hideCursor() : ansi.showCursor()}`);
	};

	this.redrawRows = function(startRow, endRow) {
		self.toggleTextCursor('hide');

		const startIndex	= self.getTextLinesIndex(startRow);
		const endIndex		= Math.min(self.getTextLinesIndex(endRow), self.textLines.length);
		const absPos		= self.getAbsolutePosition(startRow, 0);

		for(let i = startIndex; i < endIndex; ++i) {
			self.client.term.write(
				`${self.getSGRFor('text')}${ansi.goto(absPos.row++, absPos.col)}${self.getRenderText(i)}`, 
				false	//	convertLineFeeds
			);
		}

		self.toggleTextCursor('show');

		return absPos.row - self.position.row;	//	row we ended on
	};

	this.eraseRows = function(startRow, endRow) {
		self.toggleTextCursor('hide');

		const absPos		= self.getAbsolutePosition(startRow, 0);
		const absPosEnd		= self.getAbsolutePosition(endRow, 0);
		const eraseFiller	= ' '.repeat(self.dimens.width);//new Array(self.dimens.width).join(' ');

		while(absPos.row < absPosEnd.row) {
			self.client.term.write(
				`${ansi.goto(absPos.row++, absPos.col)}${eraseFiller}`,
				false	//	convertLineFeeds
			);
		}

		self.toggleTextCursor('show');
	};
<commit_msg>Remove rest to text in MLTEV for now<commit_after>/* jslint node: true */
'use strict';

const View			= require('./view.js').View;
const strUtil		= require('./string_util.js');
const ansi			= require('./ansi_term.js');
const colorCodes	= require('./color_codes.js');
const wordWrapText	= require('./word_wrap.js').wordWrapText;

const assert		= require('assert');
const _				= require('lodash');

//	:TODO: Determine CTRL-* keys for various things
	//	See http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
	//	http://wiki.synchro.net/howto:editor:slyedit#edit_mode
	//	http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_win.html

	/* Mystic
	 [^B]  Reformat Paragraph            [^O]  Show this help file
       [^I]  Insert tab space              [^Q]  Enter quote mode
       [^K]  Cut current line of text      [^V]  Toggle insert/overwrite
       [^U]  Paste previously cut text     [^Y]  Delete current line


                            BASIC MOVEMENT COMMANDS

                  UP/^E       LEFT/^S      PGUP/^R      HOME/^F
                DOWN/^X      RIGHT/^D      PGDN/^C       END/^G
*/

//
//	Some other interesting implementations, resources, etc.
//
//	Editors - BBS
//	*	https://github.com/M-griffin/Enthral/blob/master/src/msg_fse.cpp
//
//
//	Editors - Other
//	*	http://joe-editor.sourceforge.net/
//	* 	http://www.jbox.dk/downloads/edit.c
//	*	https://github.com/dominictarr/hipster
//
//	Implementations - Word Wrap
//	*	https://github.com/protomouse/synchronet/blob/93b01c55b3102ebc3c4f4793c3a45b8c13d0dc2a/src/sbbs3/wordwrap.c
//
//	Misc notes
//	* https://github.com/dominictarr/hipster/issues/15 (Deleting lines/etc.)
//
//	Blessed
//		insertLine: CSR(top, bottom) + CUP(y, 0) + IL(1) + CSR(0, height)
//		deleteLine: CSR(top, bottom) + CUP(y, 0) + DL(1) + CSR(0, height)
//	Quick Ansi -- update only what was changed:
//	https://github.com/dominictarr/quickansi

//
//	To-Do
//
//	* Index pos % for emit scroll events
//	* Some of this shoudl be async'd where there is lots of processing (e.g. word wrap)
//	* Fix backspace when col=0 (e.g. bs to prev line)
//	* Add back word delete
//	*


var SPECIAL_KEY_MAP_DEFAULT = {
	'line feed'		: [ 'return' ],
	exit			: [ 'esc' ],
	backspace		: [ 'backspace' ],
	delete			: [ 'del' ],
	tab				: [ 'tab' ],
	up				: [ 'up arrow' ],
	down			: [ 'down arrow' ],
	end				: [ 'end' ],
	home			: [ 'home' ],
	left			: [ 'left arrow' ],
	right			: [ 'right arrow' ],
	'delete line'	: [ 'ctrl + y' ],
	'page up'		: [ 'page up' ],
	'page down'		: [ 'page down' ],
	insert			: [ 'insert', 'ctrl + v' ],
};

exports.MultiLineEditTextView	= MultiLineEditTextView;

function MultiLineEditTextView(options) {
	if(!_.isBoolean(options.acceptsFocus)) {
		options.acceptsFocus = true;
	}

	if(!_.isBoolean(this.acceptsInput)) {
		options.acceptsInput = true;
	}

	if(!_.isObject(options.specialKeyMap)) {
		options.specialKeyMap = SPECIAL_KEY_MAP_DEFAULT;
	}

	View.call(this, options);

	var self = this;

	//
	//	ANSI seems to want tabs to default to 8 characters. See the following:
	//	* http://www.ansi-bbs.org/ansi-bbs2/control_chars/
	//	* http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
	//
	//	This seems overkill though, so let's default to 4 :)
	//	:TODO: what shoudl this really be? Maybe 8 is OK
	//
	this.tabWidth			= _.isNumber(options.tabWidth) ? options.tabWidth : 4;

	this.textLines			= [ ];
	this.topVisibleIndex	= 0;
	this.mode				= options.mode || 'edit';	//	edit | preview | read-only

	if ('preview' === this.mode) {
		this.autoScroll = options.autoScroll || true;
		this.tabSwitchesView = true;
	} else {
		this.autoScroll = options.autoScroll || false;
		this.tabSwitchesView = options.tabSwitchesView || false;
	}
	//
	//	cursorPos represents zero-based row, col positions
	//	within the editor itself
	//
	this.cursorPos			= { col : 0, row : 0 };

	this.getSGRFor = function(sgrFor) {
		return {
			text : self.getSGR(),
		}[sgrFor] || self.getSGR();
	};

	this.isEditMode = function() {
		return 'edit' === self.mode;
	};

	this.isPreviewMode = function() {
		return 'preview' === self.mode;
	};

	//	:TODO: Most of the calls to this could be avoided via incrementRow(), decrementRow() that keeps track or such
	this.getTextLinesIndex = function(row) {
		if(!_.isNumber(row)) {
			row = self.cursorPos.row;
		}
		var index = self.topVisibleIndex + row;
		return index;
	};

	this.getRemainingLinesBelowRow = function(row) {
		if(!_.isNumber(row)) {
			row = self.cursorPos.row;
		}
		return self.textLines.length - (self.topVisibleIndex + row) - 1;
	};

	this.getNextEndOfLineIndex = function(startIndex) {
		for(var i = startIndex; i < self.textLines.length; i++) {
			if(self.textLines[i].eol) {
				return i;
			}
		}
		return self.textLines.length;
	};

	this.toggleTextCursor = function(action) {
		self.client.term.rawWrite(`${self.getSGRFor('text')}${'hide' === action ? ansi.hideCursor() : ansi.showCursor()}`);
	};

	this.redrawRows = function(startRow, endRow) {
		self.toggleTextCursor('hide');

		const startIndex	= self.getTextLinesIndex(startRow);
		const endIndex		= Math.min(self.getTextLinesIndex(endRow), self.textLines.length);
		const absPos		= self.getAbsolutePosition(startRow, 0);

		for(let i = startIndex; i < endIndex; ++i) {
			//${self.getSGRFor('text')}
			self.client.term.write(				
				`${ansi.goto(absPos.row++, absPos.col)}${self.getRenderText(i)}`, 
				false	//	convertLineFeeds
			);
		}

		self.toggleTextCursor('show');

		return absPos.row - self.position.row;	//	row we ended on
	};

	this.eraseRows = function(startRow, endRow) {
		self.toggleTextCursor('hide');

		const absPos		= self.getAbsolutePosition(startRow, 0);
		const absPosEnd		= self.getAbsolutePosition(endRow, 0);
		const eraseFiller	= ' '.repeat(self.dimens.width);//new Array(self.dimens.width).join(' ');

		while(absPos.row < absPosEnd.row) {
			self.client.term.write(
				`${ansi.goto(absPos.row++, absPos.col)}${eraseFiller}`,
				false	//	convertLineFeeds
			);
		}

		self.toggleTextCursor('show');
	};

{'fraction_non_alphanumeric': 0.16374557722373076, 'fraction_numerical': 0.007570147288735292, 'mean_word_length': 3.0950134770889486, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 10, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6866886', 'n_tokens_mistral': 4865, 'n_tokens_neox': 4493, 'n_words': 886}