/* Various editing functions such as word wrap, to upper, etc.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */



#include "ne.h"

/* The number of type of brackets we recognize. */

#define NUM_BRACKETS 4



/* This function applies a given "to" function to the word the cursor is on.
"to" is usually toupper() or tolower(). PORTABILITY PROBLEM: they have to be
functions, and not macros. The cursor is moved one character after the space
which terminates the word. */

static int to_something(buffer *b, int (to)(int)) {

	int c, x = b->cur_x;

	assert_buffer(b);

	/* If we are after the end of the line, just return ERROR. */

	if (b->cur_line == b->line_num -1 && b->cur_pos >= b->cur_line_desc->line_len)
		return(ERROR);

	start_undo_chain(b);

	/* ??????? It would be much better to delete and insert the whole word */

	while (b->cur_pos < b->cur_line_desc->line_len && isalpha(b->cur_line_desc->line[b->cur_pos])) {
		c = b->cur_line_desc->line[b->cur_pos];
		delete_char(b, b->cur_line_desc, b->cur_line, b->cur_pos);
		insert_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, to(c));
		char_right(b);
	}

	update_partial_line(b, b->cur_y, x, TRUE);

	end_undo_chain(b);

	char_right(b);
	
	return(OK);
}



/* These functions upper or lower the case of the word the cursor is on.
They just call to_something(). */

int to_upper(buffer *b) {
	return(to_something(b, toupper));
}

int to_lower(buffer *b) {
	return(to_something(b, tolower));
}



/* This function upper cases the first letter, and lower cases the following
ones, of the word the cursor is on. The cursor is moved as in to_something(). */

int capitalize(buffer *b) {

	int c, first_char = TRUE, x = b->cur_x;

	assert_buffer(b);

	/* If we are after the end of the line, just return ERROR. */

	if (b->cur_line == b->line_num - 1 && b->cur_pos >= b->cur_line_desc->line_len)
		return(ERROR);

	start_undo_chain(b);

	while (b->cur_pos < b->cur_line_desc->line_len && isalpha(b->cur_line_desc->line[b->cur_pos])) {
		c = b->cur_line_desc->line[b->cur_pos];
		delete_char(b, b->cur_line_desc, b->cur_line, b->cur_pos);
		insert_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, (first_char ? toupper : tolower)(c));
		if (first_char) first_char = FALSE;
		char_right(b);
	}

	update_partial_line(b, b->cur_y, x, TRUE);

	end_undo_chain(b);

	char_right(b);
	
	return(OK);
}



/* This function tries to find out what bracket matches the bracket under
the cursor, and moves it there. Various error codes can be returned. */

int match_bracket(buffer *b) {

	static unsigned char bracket_table[NUM_BRACKETS][2] = { { '(',')' }, { '[',']' }, { '{','}' }, { '<','>' } };

	int i, j, n, y, dir;
	line_desc *ld = b->cur_line_desc;
	unsigned char *p;

	if (b->cur_pos >= ld->line_len) return(NOT_ON_A_BRACKET);

	for(i=0; i<NUM_BRACKETS; i++) {
		for(j=0; j<2; j++)
			if (ld->line[b->cur_pos] == bracket_table[i][j]) break;
		if (j < 2) break;
	}

	if (i==NUM_BRACKETS && j==2) return(NOT_ON_A_BRACKET);

	if (j) dir = -1;
	else dir = 1;

	n = 0;
	p = ld->line+b->cur_pos;
	y = b->cur_line;

	while(ld->ld_node.next && ld->ld_node.prev) {

		if (p) {
			while(p >= ld->line && p - ld->line < ld->line_len) {

				if (*p == bracket_table[i][j]) n++;
				else if (*p == bracket_table[i][1-j]) n--;

				if (n == 0) {
					goto_line(b, y);
					goto_pos(b, p-ld->line);
					return(OK);
				}
				p += dir;
			}
		}

		p = NULL;

		if (dir == 1) {
			ld = (line_desc *)ld->ld_node.next;
			if (ld->ld_node.next && ld->line) p = ld->line;
			y++;
		}
		else {
			ld = (line_desc *)ld->ld_node.prev;
			if (ld->ld_node.prev && ld->line) p = ld->line + ld->line_len - 1;
			y--;
		}
	}

	return(CANT_FIND_BRACKET);
}



/* This function breaks a line at the first possible position before the
current cursor position (i.e., at a tab or at a space). The space is
deleted, and a new line is inserted. The cursor is repositioned coherently.
The number of characters existing on the new line is returned, or 0 if
no word wrap was possible. */

int word_wrap(buffer *b) {

	int cur_pos, pos;

	if (!(cur_pos = pos = b->cur_pos)) return(ERROR);

	while(pos && !isspace(b->cur_line_desc->line[pos])) pos--;

	if (!pos) return(ERROR);

	delete_char(b, b->cur_line_desc, b->cur_line, pos);
	insert_lin(b, b->cur_line_desc, b->cur_line, pos);

	return(cur_pos-pos-1);
}



/* This function reformat a paragraph following the current parameters
for right_margin (a value of 0 forces the use of the full screen width).
The cursor is positioned on the first empy line after the paragraph. */

int paragraph(buffer *b) {

	int pos, line = b->cur_line,
		right_margin = b->right_margin ? b->right_margin : columns;
	line_desc *ld = b->cur_line_desc;

	if (!ld->line) {
		line_down(b);
		return(OK);
	}

	start_undo_chain(b);

	do {
		if (calc_len(ld, ld->line_len, b->tab_size) > right_margin) {

			pos = calc_pos(ld, right_margin, b->tab_size)-1;

			while(pos >= 0 && !isspace(ld->line[pos])) pos--;
			while(pos >= 0 && isspace(ld->line[pos])) pos--;

			pos++;

			if (pos) insert_lin(b, ld, line, pos);

			ld = (line_desc *)ld->ld_node.next;
			line++;

			pos = 0;
			while(pos<ld->line_len && isspace(ld->line[pos])) pos++;
			if (pos) delete_stream(b, ld, line, 0, pos);
			if (!ld->line_len) delete_char(b, ld, line, 0);
		}
		else {
			if (!ld->ld_node.next || !ld->ld_node.next->next || !((line_desc *)ld->ld_node.next)->line || isspace(((line_desc *)ld->ld_node.next)->line[0])) break;

			assert(ld->line_len>0);

			pos = ld->line_len;
			while(pos && isspace(ld->line[pos-1])) pos--;

			if (pos < ld->line_len) delete_stream(b, ld, line, pos, ld->line_len-pos);

			if (ld->line_len) insert_char(b, ld, line, ld->line_len, ' ');
			delete_char(b, ld, line, ld->line_len);
		}
	} while(ld->line && ld->ld_node.next);

	end_undo_chain(b);

	update_window_lines(b, b->cur_y, lines-2, FALSE);

	goto_line(b, line);
	line_down(b);

	return(ld->ld_node.next ? 0 : -1);
}



/* This function centers the current line with respect to the right_margin
parameter. ERROR is returned when centering the last line of text. */

int center(buffer *b) {

	int pos = 0, right_margin = b->right_margin ? b->right_margin : columns;

	while(pos<b->cur_line_desc->line_len && isspace(b->cur_line_desc->line[pos])) pos++;

	if (pos == b->cur_line_desc->line_len) return(OK);

	start_undo_chain(b);

	delete_stream(b, b->cur_line_desc, b->cur_line, 0, pos);
	insert_spaces(b, b->cur_line_desc, b->cur_line, 0, (right_margin - b->cur_line_desc->line_len)/2);

	end_undo_chain(b);

	return(OK);
}


/* This function indents a line of the amount of spaces present of
the previous line. The number of inserted characters is returned. */

int auto_indent_line(buffer *b) {

	int i;
	line_desc *ld = (line_desc *)b->cur_line_desc->ld_node.prev;
	unsigned char  *p;

	assert(ld->ld_node.prev != NULL);
	assert_line_desc(ld);

	if (!(p = ld->line)) return(0);

	while(p-ld->line < ld->line_len && isspace(*p)) p++;

	start_undo_chain(b);

	for(i=p-ld->line-1; i>=0; i--)
		insert_char(b, b->cur_line_desc, b->cur_line, 0, ld->line[i]);

	end_undo_chain(b);

	return(p-ld->line);
}
