/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Search and replace.

    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

*/

/* successful searches will always be shown in this line */
mp.config.move_seek_to_line = 5;

/* editor actions */

mp.actions['seek'] 	= sub (d) {
	local t = mp.form( [
		{ 'label'	=> L("Text to seek:"),
		  'type'	=> 'text',
		  'history'	=> 'search' },
		{ 'label'	=> L("Case sensitive") ~ ':',
		  'type'	=> 'checkbox',
		  'value'	=> mp.config.case_sensitive_search }
	] );

	if (t != NULL) {
		mp.config.case_sensitive_search = t[1];

		mp.long_op(mp.search, d, mp.backslash_codes(t[0])) ||
			mp.alert(L("Text not found."));
	}
};

mp.actions['seek_next']	= sub (d) { mp.long_op(mp.search, d, NULL) ||
		mp.alert(L("Text not found."));
	};

mp.actions['seek_prev']	= sub (d) { mp.long_op(mp.search_back, d, NULL) ||
		mp.alert(L("Text not found."));
	};

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

	local r = mp.form( [
		{ 'label'	=> L("Replace text:"),
		  'type'	=> 'text',
		  'history'	=> 'search'},
		{ 'label'	=> L("Replace with:"),
		  'type'	=> 'text',
		  'history'	=> 'replace'},
		{ 'label'	=> L("Case sensitive") ~ ':',
		  'type'	=> 'checkbox',
		  'value'	=> mp.config.case_sensitive_search },
		{ 'label'	=> L("Global replace:"),
		  'type'	=> 'checkbox',
		  'value'	=> mp.config.global_replace }
	] );

	if (r != NULL) {
		mp.config.case_sensitive_search = r[2];
		mp.config.global_replace = r[3];

		mp.store_undo(d);
		mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
	}
};

mp.actions['seek_next_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search); };
mp.actions['seek_prev_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search_back); };

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

	local r = mp.form( [
		{ 'label'	=> L("Text to seek:"),
		  'type'	=> 'text',
		  'history'	=> 'search'},
		{ 'label'	=> L("Files to grep (empty, all):"),
		  'type'	=> 'text',
		  'history'	=> 'grep'}
	] );

	if (r != NULL && r[0] ne '') {
		local i = 0;

		if ((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/', r[1])) == NULL)
			mp.alert(L("File(s) not found."));

		if (size(r) == 0)
			mp.alert(L("Text not found."));
		else
		if (size(r) > 1) {
			/* more than one; ask for it */
			local l = mp.form( [
				{ 'label'	=> L("Grep"),
				  'type'	=> 'list',
				  'list'	=> map(sub (e) {
					sprintf("%s (%d): %s", e[0], e[1] + 1, e[2]); }, r)
				 }
				]);

			if (l == NULL)
				return;

			/* get index of the desired item */
			i = l[0];
		}

		if (size(r)) {
			/* now open the document and move there */
			d = mp.long_op(mp.open, r[i][0]);
			d.txt.x = 0;
			mp.set_y(d, r[i][1]);
		}
	}
};

/* default key bindings */

mp.keycodes['f3']		= 'seek_next';
mp.keycodes['ctrl-f3']		= 'seek_prev';
mp.keycodes['ctrl-f']		= 'seek';
mp.keycodes['ctrl-r']		= 'replace';
mp.keycodes['ctrl-page-down']	= 'seek_next_char';
mp.keycodes['ctrl-page-up']	= 'seek_prev_char';

/* action descriptions */
mp.actdesc['seek']		= LL("Search text...");
mp.actdesc['seek_next'] 	= LL("Search next");
mp.actdesc['seek_prev'] 	= LL("Search previous");
mp.actdesc['replace']		= LL("Replace...");
mp.actdesc['seek_next_char']	= LL("Move to next instance of current char");
mp.actdesc['seek_prev_char']	= LL("Move to previous instance of current char");
mp.actdesc['grep']		= LL("Grep (find inside) files...");

/* code */

sub mp.prefix_regex(str)
/* set str to be a valid regex */
{
	if (!str)
		return NULL;

	/* surround with / for the regex */
	str = '/' ~ str ~ '/';

	/* add optional case insensitivity flag */
	if (! mp.config.case_sensitive_search)
		str = str ~ 'i';

	return str;
}


sub mp.search_dir(doc, str, dir)
/* search str and put the current position there, with direction */
{
	local txt, x, y, r;

	if (str == NULL)
		str = mp.last_search;
	else {
		str = mp.prefix_regex(str);
		mp.last_search = str;
	}

	if (str == NULL)
		return NULL;

	txt = doc.txt;
	x = txt.x;
	y = txt.y;

	if (dir == -1) {
		local l;
		str = str ~ 'l';

		/* search backwards */
		if (txt.x) {
			l = splice(txt.lines[y], NULL, 0, txt.x - 1);
			l = l[1];
		}

		while (regex(str, l, 0) == NULL && y > 0)
			l = txt.lines[--y];
	}
	else {
		/* search forward */
		while (regex(str, txt.lines[y], x) == NULL && y < size(txt.lines)) {
			x = 0; y++;
		}
	}

	/* get last coords */
	r = regex();

	if (r) {
		mp.set_y(doc, y);
		mp.set_x(doc, r[0] + r[1]);

		/* set always to the same line */
		if (mp.config.move_seek_to_line != NULL &&
		   (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
			doc.txt.vy = 0;
	}

	return r;
}


sub mp.search(doc, str)
/* search str and put the current position there, downwards */
{
	mp.search_dir(doc, str, 1);
}


sub mp.search_back(doc, str)
/* search str and put the current position there, backwards */
{
	mp.search_dir(doc, str, -1);
}


sub mp.replace_1(doc, this, that)
/* searches 'this' and replaces it with 'that', once */
{
	local c;

	if ((c = mp.search(doc, this)) != NULL) {
		local txt, l;

		txt = doc.txt;

		/* substitute */
		txt.lines[txt.y] = sregex(mp.prefix_regex(this),
			txt.lines[txt.y], that, c[0]);

		/* move to correct position */
		c = regex();
		mp.set_x(doc, c[0] + c[1]);

		txt.mod++;
	}

	return c;
}


sub mp.replace(doc, this, that)
/* replaces 'this' with 'that', may be globally */
{
	local cnt = 0;

	while (mp.replace_1(doc, this, that)) {
		cnt++;

		if (!mp.config.global_replace)
			break;
	}

	mp.message = {
		'timeout' => time() + 4,
		'string'  => sprintf(L("%d replaces"), cnt)
	};
}


sub mp.seek_prev_or_next_char(doc, func)
/* moves to next or previous occurent of current char */
{
	local txt = doc.txt;

	/* get current char */
	local w = splice(txt.lines[txt.y], NULL, txt.x, 1);

	/* move one char right */
	mp.move_right(doc);

	/* search for it (mp.search() or mp.search_back()) */
	local t = mp.last_search;
	func(doc, '\' ~ w[1]);
	mp.last_search = t;

	/* move back */
	mp.move_left(doc);
}


sub mp.grep(rx, spec)
/* Greps str in the files in spec. Returns NULL if no file matched the glob()
   (or glob() is unsupported), an empty list if the string was not found or
   an array with the matches, that are three-element arrays with the file name,
   the line number and the line that matched */
{
	local r, all;

	/* if spec is empty, set as NULL (meaning "glob everything") */
	if (spec eq '')
		spec = NULL;

	all = glob(spec);

	/* spec globs to NULL or empty; abort */
	if (size(all) == 0)
		return NULL;

	r = [];

	foreach (fn, all) {
		local f;

		if ((f = open(fn, "r")) != NULL) {
			local l, n;

			/* file open; now grep */
			while (l = read(f)) {
				l = mp.chomp(l);

				if (regex(rx, l)) {
					/* found; store line, filename and linenum */
					push(r, [ fn, n, l ]);
				}

				n++;
			}

			close(f);
		}
	}

	return r;
}
