;**
;**		BRIEF -- Basic Reconfigurable Interactive Editing Facility
;**
;**		Written by Dave Nanian and Michael Strickman.
;**
;**		Revision history:
;**		-----------------
;**		6 October 1985			Added simple functions to comment or uncomment
;**									blocks of C code.  Added "return" to the
;**									template editing system.
;**

;**		indent.m:
;**
;**		This macro performs simple automatic indentation for C programs.
;**	The "indent" macro is assigned to the Enter/Return key; each time it
;**	is pressed the indent level is automatically calculated.  It doesn't
;**	work all the time, but it does ok.
;**
;**		The following indenting styles are supported:
;**
;**		if (TRUE)
;**			a = b;
;**		else
;**			{
;**			b = a;
;**			c = b;
;**			}
;**
;**	and...
;**
;**		if (TRUE)
;**			a = b;
;**		else {
;**			b = a;
;**			c = b;
;**			}
;**----------------
;**
;**	Method description:
;**
;**		First, the init_indent macro must be called.  After initialization
;**	has taken place, the indent macro is invoked when the return key is
;**	pressed.  This macro gathers information about the two preceding
;**	non-blank lines, and makes its decision on how to indent
;**	based on that information.
;**

#include	"indent.h"

(macro init_indent
	(
		(if (first_time)
			(
				(int		amt
							_c_keymap
							_c_template
							_m_keymap
				)
				(global	amt
							_c_keymap
							_c_template
							_m_keymap
				)
				(keyboard_push)
				(assign_to_key "#13" "_c_indent")
				(assign_to_key "#3849" "_slide_in")
				(assign_to_key "%#15" "_slide_out")
				(= _c_keymap (inq_keyboard))
				(keyboard_pop 1)

				(keyboard_push)
				(assign_to_key "#13" "_c_indent")
				(assign_to_key "#3849" "_slide_in")
				(assign_to_key "%#15" "_slide_out")
				(assign_to_key " " "_c_abbrev")
				(assign_to_key "{" "_brace")
				(assign_to_key "#6683" "_just_brace")
				(assign_to_key "^s" "_just_space")
				(= _c_template (inq_keyboard))
				(keyboard_pop 1)

				(keyboard_push)
				(assign_to_key "#13" "_m_indent")
				(assign_to_key "#3849" "_slide_in")
				(assign_to_key "%#15" "_slide_out")
				(= _m_keymap (inq_keyboard))
				(keyboard_pop 1)

				(register_macro BUF_CHANGE_MACRO "indent_buf_change")
				(indent_buf_change)			
			)
		)
	)
)

;**
;**		indent_buf_change:
;**
;**		Each time a new file is edited, we call this routine to set up the
;**	indenting defaults.  C and macro files use different indenting
;**	conventions, and other files should not be indented at all.
;**

(macro indent_buf_change
	(
		(string		buf_extension
						bindent
		)
		(int			done
						loc
		)
		(= bindent (inq_environment "BINDENT"))
		(inq_names NULL buf_extension)

		(while (&& (strlen bindent) (! done))
			(
				(if (= loc (index bindent ":"))
					(
						(if (== buf_extension (substr bindent 1 (- loc 1)))
							(
								(if (== "s" (substr bindent (+ loc 1) 1))
									(use_local_keyboard _c_keymap)
								;else
									(if (== "t" (substr bindent (+ loc 1) 1))
										(use_local_keyboard _c_template)
									;else
										(use_local_keyboard _m_keymap)
									)
								)
								(save_position)
								(beginning_of_line)
								(= amt (distance_to_tab))
								(restore_position)
								(++ done)
							)
						;else
							(
								(if (= loc (index bindent "\;"))
									(= bindent (substr bindent (+ loc 1)))
								;else
									(= bindent "")
								)
							)
						)
					)
				;else
					(
						(message "Invalid BINDENT environment variable.")
						(= bindent "")
					)
				)
			)
		)
	)
)

;**
;**		This macro is used for .c files.
;**

(macro _c_indent
	(
		(int		curr_indent_col		;** Current unmodified indent col.
					following_position	;** Column of end of line following cursor.
					curr_col					;** Column cursor is on when called.
					curr_line				;** Column line is on when called.
					what_is_char_1			;** End of first line's character identifier.
					what_is_char_2			;** End of second line's indentifier.
		)
		(string	following_string)		;** All characters following the cursor.

;**
;**		Gather information on the two previous non-blank lines.
;**

		(inq_position curr_line curr_col)
		(end_of_line)
		(inq_position NULL following_position)
		(= following_position (- curr_col (-- following_position)))

		;**		If there are characters following the cursor, save them in
		;**	following_string.

		(if (<= following_position 0)
			(
				(int		non_blank_pos)

				(move_rel 0 -1)
				(drop_anchor)
				(move_rel 0 following_position)
				(= following_string (read))

				(if (= non_blank_pos (search_string "[~ \t]" following_string))
					(= following_string (substr following_string non_blank_pos))
				)
				(delete_block)
			)
		)
		(if (search_back "<*\\c[~ \t\n]")
			(
				(inq_position NULL curr_indent_col)
				(end_of_line)
				(search_back "[~ \t\n]")
				(= what_is_char_2 (index "\;{}" (read 1)))
				(next_char)
				(delete_to_eol)
				(beginning_of_line)

				(if (search_back "[~ \\t\\n][ \\t]@>")
					(= what_is_char_1 (index "\;{}" (read 1)))
				;else
					(= what_is_char_1 SEMI_COLON)
				)
			)
		;else
			(
				(= what_is_char_1 (= what_is_char_2 SEMI_COLON))
				(= curr_indent_col 1)
			)
		)
		(move_abs curr_line curr_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 (== what_is_char_2 OPEN_BRACE)
				(+= curr_indent_col amt)
			)
		;else
			(
				(if (== what_is_char_2 SEMI_COLON)
					(if (! what_is_char_1)
						(-= curr_indent_col amt)
					)
				;else
					(if (== what_is_char_2 CLOSE_BRACE)
						(-= curr_indent_col amt)
					;else
						(if (! what_is_char_2)
							(+= curr_indent_col amt)
						;else
							(if (&& what_is_char_1 (== what_is_char_2 OPEN_BRACE))
								(+= curr_indent_col amt)
							)
						)
					)
				)
			)
		)
		(if (< curr_indent_col 1)
			(= curr_indent_col 1)
		)
		(insert "\n")
		(move_abs 0 curr_indent_col)

		(if (<= following_position 0)
			(
				(= following_string (substr following_string 1 (- (strlen following_string) 1)))
				(insert following_string)
				(move_abs 0 curr_indent_col)
			)
		)
	)
)

;**
;**		_m_indent:
;**
;**		This macro is used for .m files.  It can also be used to indent any
;**	files that line up under the same column of the previous line (.asm
;**	files, for example).
;**

(macro _m_indent
	(
		(int		curr_indent_col
					following_position
					curr_col
					curr_line
		)
		(string	following_string)

;**
;**		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.
;**
;**		Get column of last non-blank line.
;**

		(inq_position curr_line curr_col)
		(end_of_line)
		(inq_position NULL following_position)
		(= following_position (- curr_col (-- following_position)))

		(if (<= following_position 0)
			(
				(int		non_blank_pos)

				(move_rel 0 -1)
				(drop_anchor)
				(move_rel 0 following_position)
				(= following_string (read))

				(if (= non_blank_pos (search_string "[~ \t]" following_string))
					(= following_string (substr following_string non_blank_pos))
				)
				(delete_block)
			)
		)
		(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 return,
;**	indent, line split, and clean up.
;**

		(insert "\n")
		(move_abs 0 curr_indent_col)

		(if (<= following_position 0)
			(
				(= following_string (substr following_string 1 (- (strlen following_string) 1)))
				(insert following_string)
				(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 (-->|).
;**

(macro _slide_in
	(
		(if (inq_marked)
			(
				(int			curr_line
								curr_col
								mark_line
								mark_col
								start_line
								end_line
								num_lines
				)
				(inq_position curr_line curr_col)
				(swap_anchor)
				(inq_position mark_line mark_col)

				(if (above mark_line curr_line)
					(
						(= end_line mark_line)
						(= start_line curr_line)
					)
				;else
					(
						(= start_line mark_line)
						(= end_line curr_line)
					)
				)
				(= num_lines (+ (- end_line start_line) 1))
				(move_abs start_line 1)
				(raise_anchor)

				(while num_lines
					(
						(if (!= (read 1) "\n")
							(self_insert)
						)
						(move_rel 1 0)
						(move_abs 0 1)
						(-- num_lines)
					)
				)
				(move_abs mark_line mark_col)
				(drop_anchor)
				(move_abs curr_line curr_col)
			)
		;else
			(self_insert)
		)
	)
)

;**
;**		_slide_out:
;**
;**		This macros slides a marked block of text out one tab stop per press
;**	of the back-tab key (|<--).
;**

(macro _slide_out
	(
		(int			curr_line
						curr_col
						mark_line
						mark_col
						start_line
						end_line
						num_lines
		)
		(if (inq_marked)
			(
				(inq_position curr_line curr_col)
				(swap_anchor)
				(inq_position mark_line mark_col)

				(if (above mark_line curr_line)
					(
						(= end_line mark_line)
						(= start_line curr_line)
					)
				;else
					(
						(= start_line mark_line)
						(= end_line curr_line)
					)
				)
				(= num_lines (+ (- end_line start_line) 1))
				(move_abs start_line 1)
				(raise_anchor)

				(while num_lines
					(
						(if (index " \t" (read 1))
							(delete_char)
						)
						(move_rel 1 0)
						(-- num_lines)
					)
				)
				(move_abs mark_line mark_col)
				(drop_anchor)
				(move_abs curr_line curr_col)
			)
		;else
			(error "No marked block.")
		)
	)
)

;**		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 Ctrl-{.
;**

;**
;**		_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.
;**

(macro _c_abbrev
	(
		(if (== (read 1) "\n")
			(
				(string		line)

				(int			loc)

				(save_position)
				(beginning_of_line)
				(= line (read))
				(restore_position)

				(if (&& (= loc (search_string "[~ \t\n]" line)) (< (- (strlen line) loc) 8))
					(
						(int		length)

						(= line (+ "á" (substr line loc)))
						(= line (substr line 1 (- (strlen line) 1)))

						(if (&& (< MIN_ABBREV (strlen line))
								(= loc (search_string line "áelse áelse if ()áif ()áwhile ()áfor ()ádoáswitch ()ácase :áreturn á" length 0)))
							(
								(string		completion)

								(= completion (substr "áelse áelse if ()áif ()áwhile ()áfor ()ádoáswitch ()ácase :áreturn á" (+ loc length)))
								(= completion (substr completion 1 (- (index completion "á") 1)))
								(insert completion)

								(if (== (substr line 2 1) "d")
									(
										(open_line)
										(_brace)
										(save_position)
										(down)
										(open_line)
										(insert "while ()\;")
										(restore_position)
									)
								;else
									;**
									;**		Don't back up for "else" and "return".
									;**

									(if (&& (!= loc 1) (!= loc 60))
										(prev_char)
									)
								)
							)
						;else
							(self_insert)
						)
					)
				;else
					(self_insert)
				)
			)
		;else
			(self_insert)
		)
	)
)

;**
;**		_brace:
;**
;**		This function checks to see if the typed brace should be indented
;**	and matched to the current indenting level.
;**

(macro _brace
	(
		(string		line)

		(save_position)
		(prev_char)
		(search_back "<|[~ \t]")
		(= line (read 1))
		(restore_position)
		(insert "{")

		(if (&& (! (search_string "[~ \t\n]" (read))) (index ")\t\n " line))
			(
				(int		col)

				(inq_position NULL col)

				(_c_indent)
				(save_position)
				(insert "\n")
				(restore_position)
				(save_position)
				(down)

				(if (== col 2)
					(beginning_of_line)
				)
				(insert "}")
				(restore_position)
			)
		)
	)
)

;**
;**		_just_brace, _just_space:
;**
;**		These functions insert unexpanded braces and spaces.
;**

(macro _just_brace
	(insert "{")
)

(macro _just_space
	(insert " ")
)

;**
;**		comment_block:
;**
;**		This macro comments out a block of code.  It tries to be intelligent
;**	about things like comments inside the block -- if it finds one, it
;**	escapes it and moves on.
;**
;**		Two different types of blocks are created, depending on the cursor
;**	start and end positions.  If both are in column one, comment_block
;**	assumes a "block" type comment is desired.  Block comments have "**"
;**	inserted in rows other than the first and last.
;**
;**		If the start and end columns are not both in column one, the area is
;**	simply bracketed with "/*" and "*/".
;**

(macro comment_block
	(
		(if (inq_marked)
			(
				(int		start_line
							start_col
							end_line
							end_col
							curr_col
				)
				(string	pattern)

				(inq_position start_line start_col)
				(swap_anchor)
				(inq_position end_line end_col)

				(if (|| (above start_line end_line)
								(&& (== start_line end_line) (above start_col end_col)))
					(swap_anchor)
				)
				(insert "á")
				(swap_anchor)
				(raise_anchor)

				(if (== start_col 1)
					(insert "/* ")
				;else
					(insert " /* ")
				)
				(if (&& (== start_col 1) (== end_col 1))
					(= pattern "{/\\*}|{\\*/}|{\\*\\*}|<|á")
				;else
					(= pattern "{/\\*}|{\\*/}|{\\*\\*}|á")
				)
				(while (&& (search_fwd pattern) (!= "á" (read 1)))
					(
						(inq_position NULL curr_col)

						(if (&& (== curr_col 1) (&& (== start_col 1) (== end_col 1)))
							(insert "**\t")
						;else
							(
								(insert "\\")
								(right)
							)
						)
					)
				)
				(delete_char)

				(if (== end_col 1)
					(insert "*/ ")
				;else
					(insert " */ ")
				)
			)
		;else
			(error "No marked block.")
		)
	)
)

;**
;**		uncomment_block:
;**
;**		This routine uncomments a block of code that was commented out by
;**	comment_block.  It removed the comment and leading "**" characters (if
;**	appropriate), and restores the internal comments to their original,
;**	un-escaped state.
;**

(macro uncomment_block
	(
		(if (inq_marked)
			(
				(int		start_line
							start_col
							end_line
							end_col
				)
				(inq_position start_line start_col)
				(swap_anchor)
				(inq_position end_line end_col)

				(if (|| (above start_line end_line)
								(&& (== start_line end_line) (above start_col end_col)))
					(swap_anchor)
				)
				(insert "á")
				(swap_anchor)
				(raise_anchor)

				(while (!= (read 1) "á")
					(
						(search_fwd "{</\\* }|{<\\*/ }|{ /\\* }|{<\\*\\*\t}|{ \\*/ }|{\\\\\\*}|{\\\\/\\*}|á")

						(if (!= (read 1) "á")
							(
								(if (== (read 1) "\\")
									(
										(delete_char)
										(right)
									)
								;else
									(
										(drop_anchor)

										(if (|| (!= (read 1) " ") (== (read 2) "**"))
											(move_rel 0 2)
										;else
											(move_rel 0 3)
										)
										(delete_block)
									)
								)
							)
						)
					)
				)
				(delete_char)
			)
		;else
			(error "No marked block.")
		)
	)
)
