/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Editing.

    Copyright (C) 1991-2007 Angel Ortega <angel@triptico.com>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    http://www.triptico.com

*/


/* editor actions */

mp.actions['insert_line']	= sub (d) {
	mp.store_undo(d);
	mp.insert_newline(d);

	if (d.syntax == NULL) mp.detect_syntax(d);
};

mp.actions['delete_line']	= sub (d) { mp.store_undo(d); mp.delete_line(d); };
mp.actions['insert_space']	= sub (d) { mp.store_undo(d); mp.insert_space(d); };
mp.actions['insert_tab']	= sub (d) { mp.store_undo(d); mp.insert_tab(d); };
mp.actions['delete']		= sub (d) { mp.store_undo(d); mp.delete_char(d); };
mp.actions['delete_left']	= sub (d) { mp.store_undo(d); mp.move_left(d) &&
					mp.delete_char(d); };
mp.actions['undo']		= sub (d) { mp.undo(d); };
mp.actions['redo']		= sub (d) { mp.redo(d); };
mp.actions['join_paragraph']	= sub (d) { mp.store_undo(d); mp.join_paragraph(d); };

mp.actions['word_wrap_paragraph'] = sub (d) {

	if(mp.config.word_wrap == 0)
		mp.alert(L("Word wrapping must be set"));
	else {
		mp.store_undo(d);
		mp.word_wrap_paragraph(d);
	}
};

mp.actions['line_options']	= sub (d) {

	/* convert special characters on end of line */
	local lt = mp.backslash_codes(mp.config.eol, 1);

	local t = mp.form( [
		{ 'label'	=> L("Word wrap on column (0, no word wrap):"),
		  'type'	=> 'text',
		  'value'	=> mp.config.word_wrap,
		  'history'	=> 'wordwrap' },
		{ 'label'	=> L("Automatic indentation") ~ ':',
		  'type'	=> 'checkbox',
		  'value'	=> mp.config.auto_indent },
		{ 'label'	=> L("Line termination") ~ ':',
		  'value'	=> lt,
		  'type'	=> 'text' },
		{ 'label'	=> L("Mark end of lines") ~ ':',
		  'value'	=> mp.config.mark_eol,
		  'type'	=> 'checkbox' }
	] );

	if (t != NULL) {
		mp.config.word_wrap = t[0];
		mp.config.auto_indent = t[1];
		mp.config.eol = mp.backslash_codes(t[2], 0);
		mp.config.mark_eol = t[3];
	}
};

mp.actions['tab_options']	= sub (d) {

	local t = mp.form( [
		{ 'label'	=> L("Tab size") ~ ':',
		  'type'	=> 'text',
		  'value'	=> mp.config.tab_size,
		  'history'	=> 'tabsize' },
		{ 'label'	=> L("Convert tabs to spaces") ~ ':',
		  'type'	=> 'checkbox',
		  'value'	=> mp.config.tabs_as_spaces }
	] );

	if (t != NULL) {
		mp.config.tab_size = t[0];
		mp.config.tabs_as_spaces = t[1];
	}
};

/* default key bindings */

mp.keycodes['enter']	= "insert_line";
mp.keycodes['tab']	= "insert_tab";
mp.keycodes['space']	= "insert_space";
mp.keycodes['delete']	= "delete";
mp.keycodes['backspace']= "delete_left";
mp.keycodes['ctrl-i']	= "insert_tab";
mp.keycodes['ctrl-m']	= "insert_line";
mp.keycodes['ctrl-y']	= "delete_line";
mp.keycodes['ctrl-z']	= "undo";
mp.keycodes['f4']	= "word_wrap_paragraph";
mp.keycodes['f6']	= "join_paragraph";

/* action descriptions */

mp.actdesc['insert_line']	= LL("Insert line");
mp.actdesc['delete_line']	= LL("Delete line");
mp.actdesc['insert_space']	= LL("Insert space");
mp.actdesc['insert_tab']	= LL("Insert tab");
mp.actdesc['delete']		= LL("Delete character");
mp.actdesc['delete_left']	= LL("Delete character to the left");
mp.actdesc['undo']		= LL("Undo");
mp.actdesc['redo']		= LL("Redo");
mp.actdesc['join_paragraph']	= LL("Join a paragraph in one line");
mp.actdesc['word_wrap_paragraph'] = LL("Word-wrap a paragraph");
mp.actdesc['line_options']	= LL("Line options...");
mp.actdesc['tab_options']	= LL("Tab options...");

/* code */

/**
 * mp.break_line - Breaks current line in two (inserts a newline).
 * @doc: the document
 * @col: column where the newline will be inserted 
 *
 * Breaks current line in two by inserting a newline character in between.
 * If @col is not NULL, the newline will be inserted in that column; otherwise,
 * the current x position will be used.
 */
sub mp.break_line(doc, col)
{
	local txt = doc.txt;
	local c, w;

	/* if col is NULL, set it to be the x cursor */
	if (col == NULL)
		col = txt.x;

	/* gets line where cursor is */
	c = txt.lines[txt.y];

	/* deletes from col to the end of line */
	w = splice(c, NULL, col, -1);

	/* set first part as current line */
	txt.lines[txt.y] = w[0];

	/* move to next line */
	txt.y++;

	/* insert a new line here */
	expand(txt.lines, txt.y, 1);

	/* fix the x cursor position */
	txt.x -= col;

	/* if autoindenting... */
	if (mp.config.auto_indent) {
		/* extract leading blanks in the original line
		   to prepend them to the line to be inserted */
		local i = regex("/^[ \t]*[-\+\*]?[ \t]+/", c, 0);

		/* substitute all non-tab characters with spaces */
		i = sregex("/[^\t]/g", i, " ");

		/* delete any blank in the new line */
		w[1] = sregex("/^[ \t]*/", w[1]);

		/* concatenate */
		w[1] = i ~ w[1];

		/* the x position is further the length of that */
		txt.x += size(i);
	}

	/* put second part there (or an empty string if NULL) */
	txt.lines[txt.y] = w[1] || '';

	txt.mod++;
}


sub mp.join_line(doc)
/* joins the current line with the next one */
{
	local txt = doc.txt;

	if (txt.y < size(txt.lines)) {
		/* concats current line with the next one */
		txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];

		/* delete it */
		adel(txt.lines, txt.y + 1);

		txt.mod++;
	}
}


sub mp.delete_line(doc)
/* deletes the current line */
{
	local txt = doc.txt;
	local vx;

	/* take current position */
	vx = mp.x2vx(txt.lines[txt.y], txt.x);

	/* if it's the only line, just replace it */
	if (size(txt.lines) == 1)
		txt.lines[0] = '';
	else {
		/* destroy the line */
		adel(txt.lines, txt.y);
	}

	/* fix if it was the last line */
	if (txt.y >= size(txt.lines))
		txt.y = size(txt.lines) - 1;

	/* move to previous x position */
	txt.x = mp.vx2x(txt.lines[txt.y], vx);

	txt.mod++;
}


sub mp.delete_char(doc)
/* deletes the current char */
{
	local txt = doc.txt;

	/* is it over the end of line? */
	if (txt.x == size(txt.lines[txt.y]))
		mp.join_line(doc);
	else {
		local w;

		w = splice(txt.lines[txt.y], NULL, txt.x, 1);
		txt.lines[txt.y] = w[0];
	}

	txt.mod++;
}


sub mp.delete_range(doc, bx, by, ex, ey)
/* deletes a range of characters from a document */
{
	local txt = doc.txt;

	/* move to the start of the range */
	txt.x = bx;
	txt.y = by;

	if (by == ey) {
		local w;

		/* block is just one line; delete the middle part */
		w = splice(txt.lines[by], NULL, bx, ex - bx);

		txt.lines[by] = w[0];
	}
	else {
		local w;

		/* block has more than one line */

		/* delete from the beginning to the end of the first line */
		w = splice(txt.lines[by], NULL, bx, -1);
		txt.lines[by] = w[0];

		/* delete from the beginning of the last line to
		   the end of the block */
		w = splice(txt.lines[ey], NULL, 0, ex);
		txt.lines[ey] = w[0];

		/* collapse the lines in between */
		collapse(txt.lines, by + 1, ey - by - 1);

		/* finally join both lines */
		mp.join_line(doc);
	}

	txt.mod++;
}


sub mp.insert_string(doc, str)
/* inserts a string into the cursor position */
{
	local txt = doc.txt;
	local w;

	/* splice and change */
	w = splice(txt.lines[txt.y], str, txt.x, 0);
	txt.lines[txt.y] = w[0];

	/* move right */
	txt.x += size(str);

	txt.mod++;
}


sub mp.insert(doc, a)
/* inserts an array of text into a document */
{
	local txt = doc.txt;
	local s;

	/* if a is not an array, split it */
	if (!is_array(a))
		a = split("\n", a);

	/* empty array? return */
	if ((s = size(a)) == 0)
		return;

	/* paste first line into current position */
	mp.insert_string(doc, a[0]);

	/* more than just one line? */
	if (s > 1) {
		/* break current line in two */
		mp.break_line(doc);

		/* insert last line */
		mp.insert_string(doc, a[s - 1]);
	}

	/* more than two lines? */
	if (s > 2) {
		local n = 1;

		/* open room */
		expand(txt.lines, txt.y, s - 2);

		/* transfer middle lines */
		while (n < s - 1)
			txt.lines[txt.y++] = a[n++];
	}
}


sub mp.wrap_words(doc)
/* do the word wrapping */
{
	local txt = doc.txt;

	if (mp.config.word_wrap == 0)
		return;

	/* take the column where the cursor is */
	local c = mp.x2vx(txt.lines[txt.y], txt.x);

	if (c >= mp.config.word_wrap &&
		regex("/^.{1," ~ mp.config.word_wrap ~ "}\s/", txt.lines[txt.y])) {
		local w;

		/* take the coordinates */
		w = regex();

		/* break the line there */
		mp.break_line(doc, w[1]);

		/* delete the space at the end of the line */
		txt.lines[txt.y - 1] = sregex("/\s$/", txt.lines[txt.y - 1], NULL);
	}
}


sub mp.insert_space(doc)
/* inserts a space, taking wordwrapping into account */
{
	local txt = doc.txt;

	mp.wrap_words(doc);
	mp.insert(doc, ' ');
}


sub mp.insert_tab(doc)
/* inserts a tab */
{
	if (mp.config.tabs_as_spaces) {
		/* number of spaces to insert */
		local n = mp.config.tab_size -
			((doc.txt.x) % mp.config.tab_size);

		while(n--) mp.insert(doc, ' ');
	}
	else
		mp.insert(doc, "\t");
}


sub mp.insert_newline(doc)
/* inserts a newline */
{
	mp.wrap_words(doc);
	mp.break_line(doc);
}


sub mp.insert_keystroke(doc, key)
/* inserts from a keystroke (with undo) */
{
	mp.store_undo(doc);

	if (size(key) == 1)
		mp.insert(doc, key);
	else
	if (key != NULL) {
		mp.message = {
			'timeout'	=> time() + 2,
			'string'	=> sprintf(L("Unbound keystroke '%s'"), key)
		};
	}
}


/* undo */

sub mp.store_undo(doc)
/* stores the current txt in the undo queue */
{
	queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
	doc.redo = [];
}


sub mp.undo(doc)
/* undoes last operation */
{
	local txt;

	if (txt = pop(doc.undo)) {
		queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
		doc.txt = txt;
	}
}


sub mp.redo(doc)
/* redoes last undid operation */
{
	local txt;

	if (txt = pop(doc.redo)) {
		queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
		doc.txt = txt;
	}
}

/* paragraphs */

sub mp.join_paragraph(doc)
/* joins current paragraph in just one line */
{
	local txt = doc.txt;
	local l;

	while ((l = txt.lines[txt.y + 1]) && size(l)) {
		/* delete all leading blanks in the next line */
		txt.lines[txt.y + 1] = sregex("/^[ \t]+/", txt.lines[txt.y + 1]);

		/* move to end of line and add a space separator */
		mp.move_eol(doc);
		mp.insert(doc, ' ');

		/* really join */
		mp.join_line(doc);
	}
}


sub mp.word_wrap_paragraph(doc)
/* word wraps current paragraph */
{
	local txt = doc.txt;

	if (mp.config.word_wrap == 0)
		return;

	mp.join_paragraph(doc);

	mp.move_eol(doc);

	while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
		mp.insert_space(doc);
		mp.move_left(doc);
		mp.delete_char(doc);
	}
}
