$MACRO_FILE C;

{******************************************************************************}
{                                MULTI-EDIT MACRO                              }
{                                                                              }
{Name: C_IND                                                                   }
{                                                                              }
{Description: Performs a smart indent each time the Carriage Return or Enter   }
{key is pressed.  Is called by the macro CR.                                   }
{                                                                              }
{Written by Todd M. Johnson and William M. Miller.                             }
{                                                                              }
{The algorithm used is to look at the line-ending characters (ignoring         }
{comments, including C++ '//' comments, labels, and white space) for the       }
{current and preceding lines, then decide whether and how much to change the   }
{indentation from that of the current line.  In addition, if the CR was done   }
{inside a (C-style, /* */) comment, the "line of asterisks" style of comment   }
{continuation is automatically supplied.                                       }
{******************************************************************************}

$MACRO C_IND;

	def_int(outdent_flag,
					sig_char_found,
					in_comment,  {1 = /* comment, 2 = // comment}
					curr_ind,
					start_col,
					curr_end_char,
					prev_end_char,
					in_struct,
					junk,
					jx);

	{If you want an outdent after a right brace then change the following
	 Outdent_flag to True.}

	Outdent_flag := False;

	Messages := False;
	reg_exp_stat := true;
	mark_pos;

	{ If we are before or at the first character of the first word of the line
		then leave the level the same for the line }
	start_col := c_col;
	first_word;
	if c_col >= start_col then
		set_indent_level;
		cr;
		goto mac_exit;
	end;
	goto_col(start_col);



	call skip_c_noise;

{ If the cursor is inside a comment, skip_c_noise leaves it at the opening
	'/'.  We move one character right, to align with the '*', set the indent
	level, do the carriage return, and insert a '* ' to form the line of
	asterisks continuation. }

	if (in_comment) then
		right;
		set_indent_level;
		right;
		while NOT(at_eol) and (xpos(cur_char,' |9|255',1) <> 0) do
			right;
		end;
		start_col := c_col;
		goto_mark;
		cr;

		{If you don't want the * then comment out the next line}
		if (in_comment = 1) then
			text('* ');
		end;
	{	while c_col < start_col do
			text(' ');
		end; }

		set_indent_level;
		goto mac_exit;
	end;

{ Here, we are at the top of the file, before any non-comment text.  We just
	put everything in column 1 in that case. }

	if (not(sig_char_found)) then
		goto_col(1);
		set_indent_level;
		goto_mark;
		cr;
		goto mac_exit;
	end;

{ Here we've found the nearest non-noise character.  There are four that
	specifically influence indentation decisions, so instead of carrying around
	a big string variable, we just save the index into a string of those four
	characters.  In addition, we find the first non-blank character on the line
	to determine its indentation.  Furthermore, we check if that first character
	is a '}' (probably as a result of the '} name;' at the end of a structure,
	union, or enum definition) so we can know how much to indent later. }

	curr_end_char := pos(cur_char, ';{},');

		{Special processing if we have a lone '{' on a line}
	jx := c_col;						{ store the current column position }
	first_word;
	if (c_col = jx) and (curr_end_char = 2) then
		curr_ind := 1;
		set_indent_level;
		goto do_curs;
	end;

	set_indent_level;
	curr_ind := 0;
	start_col := c_col;
	in_struct := (cur_char = '}');


{ Now we get the line ending for the preceding line. }
	call skip_c_noisex;  {no need to check to see if we're in a comment}
	if (sig_char_found) then
		prev_end_char := pos(cur_char, ';{}');
	else
		prev_end_char := 1;    { treat like semicolon }
	end;


{ This code is executed when the nearest line-ending character was a semicolon.
	}

	if (curr_end_char = 1) then     { semicolon }
		if (prev_end_char = 0) then   {if continuation line then undent twice}
			curr_ind := curr_ind - 2;
		end;
		if (outdent_flag and in_struct) then
			curr_ind := curr_ind - 1;
		end;
	else


{ This code is executed if the nearest line-ending character was a left
	brace.
	}
		if (curr_end_char = 2) then         { left brace }
			curr_ind := curr_ind + 1;
			if (prev_end_char = 0) then
				curr_ind := curr_ind - 2;       {if continuation line then undent twice}
			end;
		else
			if (curr_end_char = 3) then       { right brace }
				if (outdent_flag) then
					curr_ind := curr_ind - 1;
				end;
			else

{ Commas cause no indentation (to support enum declarations).  If the line-
	ending character was none of the above, the new line will be a continuation
	line, indented twice.  However, if the preceding line-ending character was not
	';{}', we assume that the current line was already a continuation line and no
	further indentation is necessary. }

				if ((curr_end_char <> 4) and (prev_end_char <> 0)) then
					curr_ind := curr_ind + 2;
				end;

			end;
		end;
	end;

do_curs:
{ Here we put the cursor at the correct column position, we set the indent
	level, return to the original position, and perform the carriage return. }

	jx := 0;

	goto_mark;
	cr;
	If curr_ind > 0 THEN
		While jx < curr_ind do
			jx := Jx + 1;
			indent;
		END;
	else
		While jx > curr_ind do
			jx := Jx - 1;
			undent;
		END;
	end;

	set_indent_level;
	goto mac_exit;


skip_c_noisex:
	Refresh := false;
	left;
	goto skip_c_noise1;

{ This subroutine moves the cursor to the nearest preceding non-whitespace
	character.  If there is no such character (the cursor is at the top of the
	file), the variable sig_char_found will be FALSE; otherwise, it will be
	true.  The macro also sets the variable in_comment appropriately. }

skip_c_noise:
	Refresh := false;

	left;
	mark_pos;

{ First check if we are inside a comment.  We look for the nearest of '/*' or
	'*/'; if we find the former, we are inside a comment.  In this case, we
	leave the cursor at the beginning of the comment to establish the correct
	indentation margin and return. }

comment_again:
	if (search_bwd('/',50)) THEN
		right;
		if (cur_char = '*') or (cur_char = '/') then
			pop_mark;
			if (cur_char = '/') then
				in_comment := 2;
			ELSE
				in_comment := 1;
			END;
			left;
			goto exit_skip_c;
		end;
		left;
		left;
		if (cur_char <> '*') then
			goto comment_again;
		end;
	end;
	goto_mark;
	in_comment := FALSE;

{ The following code is a loop which skips over comments (including C++ '//'
	comments) and labels, leaving the cursor on the nearest preceding non-noise
	character, if any. }

skip_c_noise1:

{ If there is a '//' on the line, we move the cursor before it and
	look some more. }

	if (search_bwd('//', 1)) then
		if ((c_col = 1) and (c_line = 1)) then
			sig_char_found := FALSE;
			goto exit_skip_c;
		end;
		left;
		goto skip_c_noise1;
	end;



{ Here we look for the nearest preceding nonblank character.  If it is a '/',
	we check if the preceding character was an asterisk; if so, we search for
	the corresponding '/*' and try again.  If the character before the '/' was
	not '*', we have found a significant character and are done. }

	if (search_bwd('[~|9 ]', 1)) then
		if (cur_char = '/') then
			left;
			if (cur_char = '*') then
				junk := search_bwd('/@*', 0);
				left;
				goto skip_c_noise1;
			end;
			right;
			sig_char_found := TRUE;
			goto exit_skip_c;
		end;

{ If the character we found was a colon, we have found a label.  We assume
	that the label was the first thing on the line and proceed to the end of
	the preceding line.  Any other character is significant. }

		if (cur_char = ':') then
			IF (C_Line = 1) THEN
				Eol;
				cr;
				Goto_Col(1);
				Indent;
				Set_Indent_Level;
				goto mac_exit;
			END;
			up;
			eol;
			goto skip_c_noise1;
		end;
		sig_char_found := TRUE;
		goto exit_skip_c;
	end;

{ If we failed to find a nonblank character on the current line, and the
	cursor is on line 1, we failed to find a significant character; otherwise,
	we back up a line and try again. }

	if (c_line = 1) then
		sig_char_found := FALSE;
		goto exit_skip_c;
	end;
	up;
	eol;
	goto skip_c_noise1;

exit_skip_c:
	refresh := true;
	ret;

mac_exit:
	Messages := True;
END_MACRO;


{******************************************************************************}
{                                MULTI-EDIT MACRO                              }
{                                                                              }
{Name: CMTCH                                                                   }
{                                                                              }
{Description: Match occurances of {} or () and handles problems with           }
{  characters embedded in comments or quotes.                                  }
{   11/01/88 11:15am  Modified for greater speed.  Also, handles screen        }
{   									updates much better.                                     }
{                                                                              }
{               (C) Copyright 1988 by American Cybernetics, Inc.               }
{******************************************************************************}
$MACRO CMTCH TRANS;

	DEF_STR( Str1,Str2,  Search_Str );  {Strings to match}

	DEF_INT( Direction,    {1 = Search forward, 0 = backward}
					 B_Count,      {Brace count.  0 = match found}
					 S_Res,        {Result of last search}
					 T_Row, T_Col, T_Line, {Holds the original position}
					 JX,           {General purpos}
					 F_Line, F_Col ); {Found position}

	Refresh := False;      {Turn screen display off}
	T_Line := C_Line;      {Store the current position}
	T_Col := C_Col;

	Push_Undo;
	Mark_Pos;

Find_Match_Str:

	IF (Cur_Char = '(') THEN    {If current char is a '(' then setup}
		Str1 := '(';
		Str2 := ')';
		Direction := 1;
		Goto Start_Match;
	END;
	IF (Cur_Char = ')') THEN    {If current char is a ')' then setup}
		Str1 := ')';
		Str2 := '(';
		Direction := 0;
		Goto Start_Match;
	END;
	IF (Cur_Char = '{') THEN    {If current char is a '{' then setup}
		Str1 := '{';
		Str2 := '}';
		Direction := 1;
		Goto Start_Match;
	END;
	IF (Cur_Char = '}') THEN    {If current char is a ')' then setup}
		Str1 := '}';
		Str2 := '{';
		Direction := 0;
		Goto Start_Match;
	END;
															{If nothing matched then }
															{  Return to original position}
	Goto_Line(T_Line);
	Goto_Col(T_Col);
	Goto_Mark;
	Make_Message('NOTHING to Match');
	Goto Macro_Exit;            {Go exit}

Start_Match:

 { Search_Str := '{\["'']}||[''"]||{/@*}||{@*/}||[' + Str1 + Str2+']'; }

	Search_Str := '["''/' + Str1 + Str2 + ']';

	Reg_Exp_Stat := True;
	Ignore_Case := True;        {Ignore the search case}
	B_Count := 1;               {Brace count starts at 1}
	S_Res := 1;                 {Init search result to true}
															{Tell the user what we're matching}
	Make_Message('Matching "'+Str1+'"...');


MATCH_LOOP:     {Main loop}

	If S_Res = 0 THEN           {If the last search was a failure then exit}
		beep;
		Goto Error_Exit;
	END;

	If B_Count = 0 THEN         {If brace count is zero then success}
		Goto Found_Exit;
	END;

			{Execute search based on direction}
			{  We are going to search not only for the match brace or paren, but
				 also for comments and double and single quotes}
	If Direction = 1 THEN
		Right;
		While (NOT (At_EOL)) and ((Cur_Char = '|255') or
					(Cur_Char = '|9')) Do
			RIGHT;
		END;
		S_Res := Search_Fwd( Search_Str, 0);
	ELSE
		Left;
		While (Cur_Char = '|255') or
					(Cur_Char = '|9') Do
			Left;
		END;
		S_Res := Search_Bwd( Search_Str , 0);
	END;

										{If the search result was a failure then exit}
	If S_Res = 0 THEN
		Goto Error_Exit;
	END;


										{If we found the original string then up the count}
	IF Found_Str = STR1 THEN
		B_Count := B_Count + 1;
		Goto Match_Loop;
	END;
										{If we found the matching string then decrement the count}
	IF Found_Str = STR2 THEN
		B_Count := B_Count - 1;
		Goto Match_Loop;
	END;

										{If we found a single quote the match it }
	IF Found_Str = '''' THEN
		Quote_Loop:
			If Direction = 1 THEN
				RIGHT;
			else
				LEFT;
			END;
			IF Direction = 1 THEN
				S_Res := Search_Fwd('{\''}||{''}',0);
			else
				S_Res := Search_Bwd('{\''}||{''}',0);
			END;
			If S_Res = 0 THEN
				Goto Error_Exit;
			END;
			If Found_Str = '\''' THEN
				Goto Quote_Loop;
			END;
			Goto Match_Loop;
	END;

											{If we found a double quote then match it}
	IF Found_Str = '"' THEN
		Quote_Loop2:
			If Direction = 1 THEN
				RIGHT;
			else
				LEFT;
			END;
			IF Direction = 1 THEN
				S_Res := Search_Fwd('{\"}||"',0);
			else
				S_Res := Search_Bwd('{\"}||"',0);
			END;
			If S_Res = 0 THEN
				Goto Error_Exit;
			END;
			If Found_Str = '\"' THEN
				Goto Quote_Loop2;
			END;
			Goto Match_Loop;
	END;

											{If we found a opening comment then match it}
	If (Found_Str = '/') then
		If (Direction = 1) then
			Right;
			IF (Cur_Char = '*') THEN
				S_Res := Search_Fwd('@*/',0);
				right; right;
			END;
			Goto Match_Loop;
		else
			if c_col > 1 then
				Left;
														{If we found a closing comment then match it}
				IF (Cur_Char = '*') THEN
						S_Res := Search_Bwd('/@*',0);
						left;
				end;
			end;
			Goto Match_Loop;
		END;
	end;



Error_Exit:       {We go here if no match was found}
	Goto_Mark;
	Make_Message('Match NOT Found.');
	Goto Macro_Exit;

Found_Exit:       {We go here if a match was found}
	F_Line := C_Line;  F_Col := C_Col;


	If C_Line > T_Line THEN
		JX := C_Line - T_Line;
	ELSE
		JX := T_Line - C_Line;
	END;

	If jx < Screen_Length then
		goto_mark;
		while  jx > 0 do
			--jx;
			if f_line > t_line then
				down;
			else
				up;
			end;
		end;
	else
		pop_mark;
	end;
	goto_line( f_line );
	goto_col( f_col );
	Make_Message('Match Found.');

Macro_Exit:
	Refresh := True;
	Redraw;
	Pop_Undo;
END_MACRO;


{******************************************************************************
																MULTI-EDIT MACRO

Name:	BUILDCMT

Description:	Builds a C comment block.

							 (C) Copyright 1988 by American Cybernetics, Inc.
******************************************************************************}

$macro buildcmt;

	def_int( ic,		{ the indent column }
					 jx,		{ general purpose }
					 jk,    { general purpose }
					 ti			{ temp insert mode }
				 );

	def_str( tstr		{ general purpose }
				 );

	push_undo;

	ti := insert_mode;
	insert_mode := true;
	mark_pos;
	while (get_line = '') and Not(At_EOF) do
		down;
		First_Word;
	end;
	if At_EOF then
		Goto_Mark;
		Text('/*  */');
		Left;
		Left;
		Left;
		Goto Exit;
	end;
	Set_Indent_Level;
	ic := C_Col;

	if (ic = 1) then
		if (search_fwd('(',1)) then
			mark_pos;
			if (search_fwd(')',0)) then
				right;
				if (cur_char <> ';') then
					goto_mark;
					goto build_header;
				end;
			end;
			goto_mark;
		end;
	end;

	Goto_Mark;
	Goto_Col( ic );

{If you don't like the fancy comment construct with the time and date, simply
remove the comments off the following 5 lines, and a very simple comment will
be created.
	Text('/*  */');
	Left;
	Left;
	Left;
	Goto Exit;
}
	text('/*');
	jx := 0;
	while (c_col < 79) and (jx < 50) do
		text('-');
		++jx;
	end;
	jk := ic + (jx / 2) - 8;
	if jk < (ic + 2) then
		jk := ic + 2;
	end;
	goto_col( jk );
	insert_mode := false;
	text( date + ' ' + str_del( time, 6, 3) );
	insert_mode := true;
	eol;

	Cr;
	Cr;
	right;
	jk := 0;
	while (jk < jx) do
		text('-');
		++jk;
	end;
	text('*/');
	up;
	home;
	text(' *');
	right;
	set_indent_level;

	goto exit;

build_header:
	first_word;
	tstr := get_word('(');
	goto_mark;
	Goto_Col( 1 );
	set_indent_level;
	text('/************************************************');
	cr;
	text(' *');
	jk := 24 - ((Length(tstr) + 8) / 2);
	if jk < 3 then
		jk := 3;
	end;
	goto_col(jk);
	text('--- ' + tstr + ' ---');
	cr;
	text(' *');
	cr;
	text(' ************************************************/');
	up;
	eol;
	indent;
Exit:
	insert_mode := ti;
	pop_undo;

end_macro;


{******************************************************************************}
{                                MULTI-EDIT MACRO                              }
{                                                                              }
{Name: CTEMP                                                                   }
{                                                                              }
{Description: Creates C language constructs based on a single character to the }
{left of the current cursor position.                                          }
{                                                                              }
{               (C) Copyright 1988 by American Cybernetics, Inc.               }
{******************************************************************************}

$MACRO CTEMP;

	DEF_INT(Temp_Col,     {Stores the old column position}
					Temp_Insert   {Stores the old insert mode}
				 );

	Def_Str(Key_Word);  {The key word to indent from}

	Push_Undo;

	Temp_Insert := Insert_Mode;   {Store insert mode}
	If (At_EOL = False) Then
		GOTO END_OF_MAC;
	End;

	Insert_Mode := True;          {Set insert mode to on}
	Temp_Col := C_COL;            {Store the current column pos}

	if (c_col = 1) then						{ignore if at beginning of line}
		goto end_of_mac;
	end;

	Left;                         {Move one char to the left}

	If (Cur_Char = '*') Then      {Check for 'do' template}
		left;
		if (cur_char = '/') then
			goto make_comment;
		end;
		goto abandon;
	End;

	if (c_col > 1) then
		left;
		if xpos(cur_char,' |9|255*+-=!\/{}()[]<>,;',1) = 0 then
			goto abandon;
		end;
		right;
	end;

	If (Cur_Char = 'i') Then      {Check for 'if' template}
		Key_Word := 'f';
		GOTO MAKE_CONSTRUCT;
	End;

		If (Cur_Char = '{') Then    {Check for '{' template}
			GOTO MAKE_BRACE;
		End;

	If (Cur_Char = 'w') Then      {Check for 'while' template}
		Key_Word := 'hile';
		GOTO MAKE_CONSTRUCT;
	End;

	If (Cur_Char = 'f') Then      {Check for 'for' template}
		Key_Word := 'or';
		GOTO MAKE_CONSTRUCT;
	End;

	If (Cur_Char = 's') Then      {Check for 'switch' template}
		GOTO MAKE_SWITCH;
	End;

	If (Cur_Char = 'd') Then      {Check for 'do' template}
		GOTO MAKE_DO_WHILE;
	End;

abandon:
	Goto_Col(Temp_Col);           {Else return to original pos and exit}
	GOTO END_OF_MAC;

MAKE_CONSTRUCT:                 {Creates a general C construct}
	Goto_Col(Temp_Col);
	Text(Key_Word + ' () {');
	Cr;
	Cr;
	Goto_Col(Temp_Col - 1);
	Text('}');
	Up;
	Goto_Col(Temp_Col);
	Indent;
	Up;
	Goto_Col(Temp_Col + Length(Key_Word) + 2);
	GOTO END_OF_MAC;

MAKE_SWITCH:                {Creates a 'switch' construct}
	Goto_Col(Temp_Col);
	Text('witch () {');
	Cr;
	Goto_Col(Temp_Col);
	Indent;
	Text('case  :');
	Cr;
	Goto_Col(Temp_Col);
	Text('}');
	Up;
	Up;
	Goto_Col(Temp_Col + 7);
	GOTO END_OF_MAC;

MAKE_DO_WHILE:                {Creates a 'do while' construct}
	Goto_Col(Temp_Col);
	Text('o {');
	Cr;
	Goto_Col(Temp_Col);
	Indent;
	Cr;
	Goto_Col(Temp_Col - 1);
	Text('} while ();');
	Up;
	Home;
	GOTO END_OF_MAC;

MAKE_BRACE:                   {Creates a matching brace}
	First_Word;
	Temp_Col := C_Col;
	Eol;
	CR;
	CR;
	Goto_Col(Temp_Col);
	Text('}');
	Up;
	left;
	Indent;
	GOTO End_OF_MAC;

MAKE_COMMENT:							{build a comment block}
	del_chars(2);
	run_macro('buildcmt');
	goto end_of_mac;

END_OF_MAC:
											{Return insert_mode to original state}
	Insert_Mode := Temp_Insert;
	Pop_Undo;

END_MACRO;
