/*
** 	BRIEF -- Basic Reconfigurable Interactive Editing Facility
**
** 	Written by Dave Nanian and Michael Strickman.
*/

/*
** 	indent.cb:
**
** 	This macro performs automatic indenting for a number of languages.
** Other languages can be supported through the standard BPACKAGES
** interface.
*/

#define MIN_ABBREV		  1			//	Increase this to specify longer
												//	default minimum abbreviations.

#define REG_MARK			  1			//	Regular marks.
#define COL_MARK			  2			//	Column marks.
#define LINE_MARK 		  3			//	Line marks.
#define NI_MARK			  4			//	Non-inclusive marks.

#define	C_SKIP_PAT		"{{/\\*{{[~*/]{/+[~*/]}@}|\\*}@\\*/}|{//*}|{<{[A-Za-z0-9_]+:}|{[ \xc\t]@#*}}|[ \xc\t\n]}@"

/*
**		ABBREV_LIST_2 contains the list of abbreviations that don't require
**	cursor positioning.  ABBREV_LIST_1 requires the cursor to back up a
**	character (usually placing it inside a parenthesis).
*/

#define	ABBREV_LIST_1	"\xffreturn \xffbreak;\xffdo\xffdefault:\xffelse \xffcase :\xffcontinue;\xff"
#define	ABBREV_LIST_2	"\xffei\xffelse if ()\xffif ()\xffwhile ()\xfffor ()\xffswitch ()\xff"

void back_tab (void);
void open_line (void);
string .c_template_first (~int, ~int, ~int, ~int);
string .c_smart_first (~int, ~int, ~int);
string .cb_template_first (~int, ~int, ~int, ~int);
string .cb_smart_first (~int, ~int, ~int);
string _regular_first (void);
int .c_indent (~int indent_query);
void r_indent (void);
void slide_in (void);
void slide_out (void);
void .c_abbrev (void);
void brace_expand (void);
void .c_open_brace (void);
void .c_close_brace (void);
void just_brace (void);
void just_cbrace (void);
void just_space (void);

int	.c_min_abbrev,			//	Minimum template abbreviation.
		.c_indent_open,		//	Location of the open brace.
		.c_indent_close,		//	Location of the close brace.
		.c_indent_first,		//	Location of the first braces.
		.c_smart,				//	C smart indenting keymap.
		.c_template,			//	C template editing keymap, expands on <Space>.
		.c_alt_template,		//	C template editing keymap, expands on <Tab>.
		_r_keymap;				//	Regular indenting keymap.

/*
** 	.c_template_first, .c_smart_first,
**		.cb_template_first, .cb_smart_first,
**		_regular_first:
**
** 	These macros are called by the BPACKAGES parser in language.m.
** They initialize the local keymaps for the various indenting functions,
** set the abbreviation length and adjust the indenting style.
*/

string .c_template_first (~int, ~int, ~int, ~int)
{
	.c_min_abbrev = MIN_ABBREV;
	get_parm (3, .c_min_abbrev);

	if (first_time())
   {
	   keyboard_push ();
	   assign_to_key ("<Enter>", ".c_indent");
	   assign_to_key ("<Tab>", ".c_abbrev");
	   assign_to_key ("<Shift-Tab>", "slide_out");
	   assign_to_key ("<{>", "brace_expand");
	   assign_to_key ("<}>", ".c_close_brace");
	   assign_to_key ("<Ctrl-{>", "just_brace");
	   assign_to_key ("<Ctrl-}>", "just_cbrace");
	   assign_to_key ("<Ctrl-s>", "just_space");
	   .c_alt_template = inq_keyboard ();
	   keyboard_pop (1);
	   keyboard_push ();
	   assign_to_key ("<Enter>", ".c_indent");
	   assign_to_key ("<Tab>", "slide_in");
	   assign_to_key ("<Shift-Tab>", "slide_out");
	   assign_to_key ("<Space>", ".c_abbrev");
	   assign_to_key ("<{>", "brace_expand");
	   assign_to_key ("<}>", ".c_close_brace");
	   assign_to_key ("<Ctrl-{>", "just_brace");
	   assign_to_key ("<Ctrl-}>", "just_cbrace");
	   assign_to_key ("<Ctrl-s>", "just_space");
	   .c_template = inq_keyboard ();
	   keyboard_pop (1);
   }
	if (.c_min_abbrev == 0)
		{
		use_local_keyboard (.c_alt_template);
		.c_min_abbrev = 1;
		}
	else
		use_local_keyboard (.c_template);

	.c_indent_open = 1;
	.c_indent_close = 1;
	.c_indent_first = 0;
	get_parm (0, .c_indent_open);
	get_parm (1, .c_indent_close);
	get_parm (2, .c_indent_first);

	if (!.c_indent_open || !.c_indent_close)
		.c_indent_first = 0;

	returns ("");
}

string .c_smart_first (~int, ~int, ~int)
{
	if (first_time())
   {
      keyboard_push ();
	   assign_to_key ("<Enter>", ".c_indent");
	   assign_to_key ("<Tab>", "slide_in");
	   assign_to_key ("<Shift-Tab>", "slide_out");
	   assign_to_key ("<{>", ".c_open_brace");
	   assign_to_key ("<}>", ".c_close_brace");
	   assign_to_key ("<Ctrl-{>", "just_brace");
	   assign_to_key ("<Ctrl-}>", "just_cbrace");
	   .c_smart = inq_keyboard ();
	   keyboard_pop (1);
   }
	use_local_keyboard (.c_smart);
	.c_indent_open = 1;
	.c_indent_close = 1;
	.c_indent_first = 0;
	get_parm (0, .c_indent_open);
	get_parm (1, .c_indent_close);
	get_parm (2, .c_indent_first);

	if (!.c_indent_open || !.c_indent_close)
		.c_indent_first = 0;

	returns ("");
}

string .cb_template_first (~int p1, ~int p2, ~int p3, ~int p4)
{
	returns ( .c_template_first(p1, p2, p3, p4) );
}

string .cb_smart_first (~int p1, ~int p2, ~int p3)
{
	returns ( .c_smart_first(p1, p2, p3) );
}


string _regular_first (void)
{
	if (first_time())
   {
	   keyboard_push ();
	   assign_to_key ("<Enter>", "r_indent");
	   assign_to_key ("<Tab>", "slide_in");
	   assign_to_key ("<Shift-Tab>", "slide_out");
	   _r_keymap = inq_keyboard ();
	   keyboard_pop (1);
   }
	use_local_keyboard (_r_keymap);
	returns ("");
}

/*
**		.c_indent:
**
**		This macro does syntax-sensitive indenting ("smart indenting") for
**	C language files.
*/

int .c_indent (~int indent_query)
{
	int	curr_indent_col, 		//	Current unmodified indent col.
			prev_indent_col, 		//	Previous unmodified indent col.
			curr_line, 				//	Line cursor is on when called.
			curr_col,				//	Column cursor is on when called.
			prev_end_char,			//	Character at end of current line.
			curr_end_char,			//	Character at end of previous line.
			indent_level; 			//	Current indenting level.

	string	following_text,	//	All characters following the cursor.
				curr_text;			//	The text of the current line.

	/*
	**		Gather information on the two previous non-blank lines.
	*/

	if (!inq_mode ())
		end_of_line ();
	else if ((following_text = ltrim (read ())) != "")
		{
		/*
		**		If there are non-whitespace characters following
		**	the cursor, we save them in following_text.  These
		** characters are re-inserted after the new indent column
		** has been determined.
		*/

		following_text = substr (following_text, 1, strlen (following_text) - 1);
		delete_to_eol ();
		}

	/*
	** 	We now search back for the previous line, skipping over
	** all intervening comments, compiler directives and labels.
	** The cursor is left one character after the character we're
	** interested in.
	*/

	prev_end_char = curr_end_char = ';';
	prev_indent_col = 1;
	inq_position (curr_line, curr_col);
	search_back (C_SKIP_PAT, -2);

	if (prev_char ())
		{
		string	line_end = read ();	//	The text after the cursor on this line.

		curr_end_char = atoi (substr (line_end, 1, 1), 0);

		/*
		**		We trim the end of the preceding line to ensure that any
		**	spaces left by template expansion of else statements (or
		**	accidental user typing) are removed.
		*/

		delete_to_eol ();

		/*
		**		If we're dealing with an open brace, later indenting code
		**	needs the entire text of the line.  We retrieve that information
		**	here.
		*/

		if (curr_end_char == '{')
			{
			save_position ();
			beginning_of_line ();
			curr_text = trim (ltrim (read ())) + "{";
			restore_position ();
			}
		insert (trim (line_end));

		/*
		**		Now we move to the first non-blank character on the
		**	line, and get that line's indent column.	We then search
		**	back for the previous line, which may or may not exist.
		**	If it doesn't exist, we assume it ends in a semi-colon --
		**	this ensures that the indent level doesn't change.
		*/

		beginning_of_line ();
		next_char (strlen (read ()) - strlen (ltrim (read ())));
		inq_position (NULL, curr_indent_col);

		if (prev_char () && search_back (C_SKIP_PAT, -2) > 1)
			{
			prev_char ();
			prev_end_char = atoi (read (1), 0);

			if (.c_indent_first && .c_indent_open)
				{
				beginning_of_line ();
				next_char (strlen (read ()) - strlen (ltrim (read ())));
				inq_position (NULL, prev_indent_col);
				}
			}
		}
	move_abs (curr_line, curr_indent_col);

	/*
	**		We've determined the last two non-blank lines' last characters
	**	as well as the column position of the first non-blank character.
	**	Now we position the cursor on the new line's proper level.
	*/

	if (curr_indent_col == 1)
		{
		if (!.c_indent_first && curr_end_char == '{')
			curr_indent_col += distance_to_tab ();
		}
	else
		{
		int prev_char_known;

		/*
		**		Some indenting methods need to know if we've
		**	actually recognized a given character at all.  Rather
		**	than duplicate the compound if statements required, we
		**	set a variable here.
		*/

		switch (prev_end_char)
			{
			case ';':
			case '{':
			case '}':
			case ':':
				++prev_char_known;

			case ')':
				--prev_char_known;
			}

		switch (curr_end_char)
			{
			/*
			**		If the current line ends with a semicolon, we make
			**	an indenting decision based on the previous line.	If
			**	it ends with a "known" character, we leave the indenting
			**	level as is.  Otherwise, we assume we've hit the end of
			**	an indented clause and outdent.
			*/

			case ';':
				{
				if (prev_char_known <= 0)
					--curr_indent_col;
				}
			/*
			**		If close braces are supposed to be indented, we
			**	outdent after encountering one.	Otherwise, we stay
			**	at the same level.
			*/

			case '}':
				{
				if (.c_indent_close)
					--curr_indent_col;
				}
			/*
			**		The rule for open braces is the opposite of that for
			**	close braces.	If they're supposed to be indented, we
			**	leave the indenting level the same (since we've already
			**	indented.
			**
			**		There is a special case, however.  If the open brace
			**	is placed on the same line as the statement, we want to
			**	indent the block.  So, we assume that if the open brace
			**	isn't the only text on the line, we're dealing with this
			**	case, and should indent.
			*/

			case '{':
				{
				if (!.c_indent_open || (curr_text != "{" && !(.c_indent_first && prev_indent_col == 1)))
					curr_indent_col += distance_to_tab ();
				}
			/*
			**		Finally, if we didn't recognize the end of the
			**	previous line, we assume we're supposed to indent.  This
			**	ends up indenting for statement continuations, case
			**	statements, if statements, etc.
			*/

			default:
				{
				if (prev_char_known)
					curr_indent_col += distance_to_tab ();
				}
			}
		}

	/*
	**		Here we move the cursor back to the original
	**	column we started at (where the newline should go).
	**	We then execute the correct command for the pressed
	**	key.	If we were assigned to "<Enter>", we insert the
	**	Enter keystroke, thereby ensuring that overstrike and
	**	insert modes are handled properly.	If not, we insert
	**	the keystroke for Ctrl-m, which always inserts a newline.
	*/

	move_abs (0, curr_col);

	/*
	**		Since <Enter> actually differently in insert and overstrike modes,
	**	we try to ensure that we do the right thing based on the command that's
	**	running.
	**
	**		We assume that a direct assignment to .c_indent means we should
	**	use "normal" <Enter> processing:  otherwise, we assume we're assigned
	**	to something like open_line, and use <Ctrl-m>.
	*/

	if (!indent_query)
		{
		if (inq_command () == ".c_indent")
			self_insert (key_to_int ("<Enter>"));
		else
			self_insert (key_to_int ("<Ctrl-m>"));
		}
	/*
	**		We now move the cursor to the new indenting level on
	**	the next line, and we retain the indenting "level" so that
	**	we can return it to the calling function.
	*/

	beginning_of_line ();
	curr_col = distance_to_tab () + 1;

	while (curr_col <= curr_indent_col)
		{
		move_abs (0, curr_col);
		++indent_level;
		curr_col += distance_to_tab ();
		}
	/*
	**		Finally, if we split the original line, we insert the
	**	characters that were following the cursor here.  They're
	**	inserted, of course, at the new, indented column.
	*/

	save_position ();
	insert (following_text);
	restore_position ();

	returns (indent_level);
}

/*
**		r_indent:
**
**		This macro does "standard" style indenting, indenting new lines
**	to the same column as the previous non-blank line.
*/

void r_indent (void)
{
	int	curr_indent_col,
			curr_line,
			curr_col;

	string	following_text;

	/*
	**		First, check to see if there are any following characters
	**	on the line.  If so, read them into a temporary variable and
	**	delete from the first line.  Then, get the column of the first
	**	non-blank line.
	*/

	if (!inq_mode ())
		end_of_line ();
	else if ((following_text = ltrim (read ())) != "")
		{
		/*
		**		If there are non-whitespace characters following
		**	the cursor, we save them in following_text.  These
		**	characters are re-inserted after the new indent column
		**	has been determined.
		*/

		following_text = substr (following_text, 1, strlen (following_text) - 1);
		delete_to_eol ();
		}
	inq_position (curr_line, curr_col);

	if (search_back ("<*\\c[~ \\t\\n]"))
		inq_position (NULL, curr_indent_col);
	else
		curr_indent_col = 1;

	move_abs (curr_line, curr_col);

	/*
	**		We've determined the last non-blank lines' indent level --
	**	do mode-sensitive return, indent, line split, and clean up.
	*/

	if (inq_command () == "r_indent")
		self_insert (key_to_int ("<Enter>"));
	else
		self_insert (key_to_int ("<Ctrl-m>"));

	move_abs (0, curr_indent_col);
	insert (following_text);
	move_abs (0, curr_indent_col);
}

/*
**		slide_in:
**
**		This macros slides a marked block of text in one tab stop per press
**	of the tab key (-->|).
*/

void slide_in (void)
{
	int	start_line,
			start_col,
			end_line,
			mark_type,
			tab_key = key_to_int ("<Tab>");

	if (mark_type = inq_marked (start_line, start_col, end_line))
		{
		int	cursor_line,
				cursor_col,
				mark_line,
				mark_col,
				num_lines,
				old_mode;

		inq_position (cursor_line, cursor_col);
		swap_anchor ();
		inq_position (mark_line, mark_col);

		if (mark_type != COL_MARK)
			start_col = 1;

		move_abs (start_line, start_col);
		raise_anchor ();

		old_mode = inq_mode ();
		insert_mode (1);

		while (start_line <= end_line)
			{
			search_fwd ("[~ \t]");

			/*
			**		Note that self_inserting the complete
			**	key definition ensures the Tab is converted
			**	to spaces if -t is used.
			*/

			if (read (1) != "\n")
				self_insert (tab_key);

			move_abs (++start_line, start_col);
			}
		move_abs (mark_line, mark_col);
		drop_anchor (mark_type);
		move_abs (cursor_line, cursor_col);
		insert_mode (old_mode);
		}
	else
		self_insert (tab_key);
}

/*
** 	slide_out:
**
** 	This macros slides a marked block of text out one tab stop per press
**	of the back-tab key (|<--).
*/

void slide_out (void)
{
	int	start_line,
			start_col,
			end_line,
			mark_type;

	if (mark_type = inq_marked (start_line, start_col, end_line))
		{
		int	num_lines;

		save_position ();

		if (mark_type != COL_MARK)
			start_col = 1;

		move_abs (start_line, start_col);

		while (start_line <= end_line)
			{
			int	curr_col;

			search_fwd ("[~ \t]");
			inq_position (NULL, curr_col);

			if (curr_col > start_col)
				{
				drop_anchor (4);
				back_tab ();
				inq_position (NULL, curr_col);

				if (curr_col < start_col)
					move_abs (0, start_col);

				delete_block ();
				}
			move_abs (++start_line, start_col);
			}
		restore_position ();
		}
	else
		back_tab ();
}

/*
** 	Template editing macros:
**
** 	These macro performs simple template editing for C programs.  Each
** time the space bar is pressed, the characters before the cursor are
** checked to see if they are "if", "else if", "while", "for", "do",
** "switch" or "case" (or abbreviations for them).  These keywords must
** only be preceded with spaces or tabs, and be typed at the end of a
** line.  If a match is found, the remainder of the statement is filled
** in automatically.
**
** 	In addition, a brace pairer is included -- this automatically
** inserts a matching brace at the proper indentation level when an
** opening brace is typed in.  To insert a brace without a matching
** brace (it attempts to be smart about matching braces, but you never
** can make this type of thing quite smart enough), type either Ctrl-{
** or quote the brace with Alt-q.
*/

/*
** 	.c_abbrev:
**
** 	This function checks to see if the characters before the space just
** typed (and actually inserted by this function) are destined to be
** followed by the remainder of a C construct.
*/

void .c_abbrev (void)
{
	int	done;

	if (read (1) == "\n")
		{
		int	loc;

		string	line;

		save_position ();
		beginning_of_line ();
		line = trim (ltrim (read ()));
		restore_position ();

		if (!index (" \t\n", substr (line, 1, 1)) && (strlen (line) && strlen (line) < 8))
			{
			int	length;

			string	abbrev_list = ABBREV_LIST_1;

			line = "\xff" + line;

			if (.c_min_abbrev < strlen (line)
					&& ((loc = search_string (line, abbrev_list, length, 0))
							|| (loc = search_string (line, abbrev_list = ABBREV_LIST_2, length, 0))))
				{
				string	completion;

				completion = substr (abbrev_list, loc + length);
				completion = substr (completion, 1, index (completion, "\xff") - 1);

				if (line + completion == "\xffei")
					{
					left ();
					delete_char ();
					completion = "lse if ()";
					}
				done = strlen (completion);
				insert (completion);

				if (line + completion == "\xffdo")
					{
					open_line ();
					brace_expand ();
					save_position ();
					move_rel (1, 0);
					open_line ();
					insert ("while ();");
					restore_position ();
					done++;
					}
				/*
				** 	Only back up if the last character of the completion is
				**	) or :.
				*/

				else if (strlen (completion) && index (":)", substr (completion, strlen (completion))))
					prev_char ();
				}
			}
		}
	if (!done)
		if (inq_local_keyboard () == .c_alt_template)
			slide_in ();
		else
			self_insert ();
}

/*
** 	brace_expand:
**
** 	This function checks to see if the typed brace should be indented
** and matched to the current indenting level.
*/

void brace_expand (void)
{
	string	line;

	save_position ();
	beginning_of_line ();
	line = read ();
	restore_position ();
	.c_open_brace ();

	if (search_string ("<|{{else}|{do}}|{{typedef}|{struct}[ \t]@[~ \t\n]@}|[):][ \t]@>", line) && "" == trim (ltrim (read ())))
		{
		int	col;

		inq_position (NULL, col);

		.c_indent ();
		save_position ();

		if (col == 2)
			insert ("\n}");
		else
			{
			inq_position (NULL, col);
			insert ("\n");

			if (.c_indent_close == 1)
				{
				move_abs (0, col);
				insert ("}");
				}
			else
				.c_close_brace ();
			}
		restore_position ();
		}
}

/*
** 	.c_open_brace:
**
** 	This function inserts an open brace at the correct column, given the
** current indenting conventions.
*/

void .c_open_brace (void)
{
	save_position ();
	beginning_of_line ();

	if (trim (ltrim (read ())) == "")
		{
		restore_position ();
		.c_indent (1);

		if (!.c_indent_open)
			back_tab ();
		else if (.c_indent_first)
			{
			int	curr_col;

			inq_position (NULL, curr_col);

			if (curr_col == 1)
				move_rel (0, distance_to_tab ());
			}
		}
	else
		restore_position ();

	self_insert ('{');
}

/*
** 	.c_close_brace:
**
** 	This function inserts a close brace at the correct column, given the
** current indenting conventions.
*/

void .c_close_brace (void)
{
	save_position ();
	beginning_of_line ();

	if ("" == trim (ltrim (read ())))
		{
		restore_position ();

		if (!.c_indent_first && .c_indent (1) == 1 || !.c_indent_close)
			back_tab ();
		}
	else
		restore_position ();

	self_insert ('}');
}

/*
** 	just_brace, just_cbrace, just_space:
**
** 	These functions insert unexpanded and expanded braces and spaces.
*/

void just_brace (void)
{
	insert ("{");
}

void just_cbrace (void)
{
	insert ("}");
}

void just_space (void)
{
	insert (" ");
}

