#include "vi.h"

#define	FORWARD	1						;** Value used to search FORWARD.
#define	BACKWARD	0						;** Value used to search BACKWARD.

(extern	vi_chr_dir						;** Last character search direction.
			vi_srch_chr						;** Last character sought.
			_dir								;** BRIEF's global search direction.
			_s_pat							;** BRIEF's last search string.
			vi_ins							;** Enter insert mode macro.
			vi_open_line					;** Insert a blank line macro.
)

;**	We have just executed a command, so clear the global variables
;**	(repeat count, prefix, scrap buffer) that applied to it
;**	before we execute the next command.

(macro vi_reset_cmd
	(
		(= vi_rep_cnt 0)
		(= vi_prefix 0)
		(= vi_buf_prefix "")
	)
)

;**	Make sure the repeat count is at least 1.

(macro rep_zero
	(if (<= vi_rep_cnt 0)
		(= vi_rep_cnt 1)
	)
)

;**	Drop a bookmark.

(macro vi_mark
	(
		(int	inchr
				ascii
		)
		(= ascii (& (= inchr (pause "vi_mark")) 0xff))

		(if (|| (< ascii '0') (> ascii '9'))
			(error "Marks must be named 0-9.")
		;else
			(drop_bookmark (- ascii '0'))
		)
		(vi_reset_cmd)
	)
)

;**	Go to a bookmark, or to the first nonwhite character after it.

(macro vi_do_mark
	(
		(int		inchr
					ascii
					type
					found
					row
					col
		)
		(= ascii (& (= inchr (pause "vi_do_mark")) 0xff))

		(if (|| (< ascii '0') (> ascii '9'))
			(error "Marks must be named 0-9.")
		;else
			(
				(get_parm 0 type)
				(inq_position row col)

				(if vi_prefix
					(drop_anchor NORMAL)
				)
				(if (= found (goto_bookmark (- ascii '0')))
					(
						(if type
							(vi_white)
						)
						(if vi_prefix
							(if (== vi_prefix YANK)
								(
									(vi_copy)
									(move_abs row col)
									(message "Yanked to mark.")
								)
							;else
								(
									(vi_cut)
									(message "Deleted to mark.")
										(if (== vi_prefix CHANGE)
											(vi_ins)
										)
								)
							)
						)
					)
				;else
					(if vi_prefix
						(raise_anchor)
					)
				)
			)
		)
		(vi_reset_cmd)
	)
)

;**	Delete a block.

(macro vi_delete_block
	(
		(if (inq_marked)
			(delete_block)
		;else
			(error "Not a marked block.")
		)
	)
)

;**	If we have no prefix, we set the right prefix.  If we have any
;**	kind of prefix, we do the whole line.

(macro vi_change
	(if (== vi_prefix CHANGE)
		(whole_line)
	;else
		(= vi_prefix CHANGE)
	)
)

(macro vi_delete
	(if (== vi_prefix DELETE)
		(whole_line)
	;else
		(= vi_prefix DELETE)
	)
)

(macro vi_yank
	(if (== vi_prefix YANK)
		(whole_line)
	;else
		(= vi_prefix YANK)
	)
)

;**	Move to end of line, performing prefix operations if necessary.

(macro vi_eol
	(
		(int		len)

		(extern	vi_append_eol)

		(if vi_prefix
			(if (== (read 1) "\n")
				(beep)
			;else
				(
					(save_position)
					(drop_anchor NORMAL)
					(end_of_line)
					(left)

					(if (== vi_prefix YANK)
						(
							(vi_copy)
							(message "Yanked to end of line.")
						)
					;else
						(
							(vi_cut)
							(message "Deleted to end of line.")
						)
					)
					(restore_position)

					(if (== vi_prefix CHANGE)
						(vi_append_eol)
					)
				)
			)
		;else
			(
				(end_of_line)
				(left)
			)
		)
		(vi_reset_cmd)
	)
)

;**	Delete to end of line (and enter insert mode to change).

(macro vi_change_to_eol
	(
		(vi_change)
		(vi_eol)
	)
)

(macro vi_delete_to_eol
	(
		(vi_delete)
		(vi_eol)
	)
)

;**	Delete <repeatcount> lines, and insert a blank line in their place.

(macro vi_subst_lines
	(
		(= vi_prefix DELETE)
		(whole_line)
		(vi_open_line TRUE)
	)
)

;**	Copy <repeatcount> lines to a scrap buffer.

(macro vi_yank_lines
	(
		(= vi_prefix YANK)
		(whole_line)
	)
)

;**	Mark the whole line, or as many lines as are indicated by the
;**	repeat count, and perform the prefix operation on it.
;**	vi_prefix is guaranteed to be one of { CHANGE, DELETE, YANK }.

(macro whole_line
	(
		(int		num_lines)

		(save_position)
		(rep_zero)
		(drop_anchor LINE)
		(= num_lines vi_rep_cnt)
		(move_rel (-- vi_rep_cnt) 0)

		(if (== vi_prefix YANK)
			(
				(vi_copy)
				(message "%d line(s) yanked." num_lines)
			)
		;else
			(
				(vi_cut)
				(message "%d fewer line(s)." num_lines)
			)
		)
		(restore_position)

		(if (== vi_prefix CHANGE)
			(vi_open_line TRUE)
		;else
			(
				(search_fwd "[~ \\t]" TRUE TRUE FALSE)
				(vi_reset_cmd)
			)
		)
	)
)

;**	Cut a marked area to the scrap.  If we have a buffer prefix (i.e.,
;**	a named scrap) put it in that buffer.

(macro vi_cut
	(
		(int		curbuf
					newbuf
					mark_type
		)
		(string	str)

		(= mark_type (inq_marked))
		(cut)

		(if (!= vi_buf_prefix "")
			(
				(= curbuf (inq_buffer))
				(= newbuf (create_buffer vi_buf_prefix vi_buf_prefix TRUE))
				(set_buffer newbuf)
				(top_of_buffer)
				(drop_anchor)
				(end_of_buffer)
				(delete_block)
				(sprintf str "%d\n" mark_type)
				(insert str)
				(paste)
				(set_buffer curbuf)
			)
		)
	)
)

;**	Copy a marked area to the scrap.  If we have a buffer prefix (i.e.,
;**	a named scrap) put it in that buffer.

(macro vi_copy
	(
		(int		curbuf
					newbuf
					mark_type
		)
		(string	str)

		(= mark_type (inq_marked))
		(copy)

		(if (!= vi_buf_prefix "")
			(
				(= curbuf (inq_buffer))
				(= newbuf (create_buffer vi_buf_prefix vi_buf_prefix TRUE))
				(set_buffer newbuf)
				(top_of_buffer)
				(drop_anchor)
				(end_of_buffer)
				(delete_block)
				(sprintf str "%d\n" mark_type)
				(insert str)
				(paste)
				(set_buffer curbuf)
			)
		)
	)
)

;**	Pastes the named scrap either before or after the current position,
;**	based on the 'after' parameter (0).

(macro vi_paste
	(
		(int		curbuf
					newbuf
					num_pastes
					mark_type
					after
		)
		(get_parm 0 after)

		;**	If we have a name for the scrap buffer, we use it; otherwise,
		;**	we use the normal scrap, which may be trashed by references
		;**	to the named buffers.  (We also use the normal scrap if the
		;**	named buffer turns out to be empty.)

		(if (!= vi_buf_prefix "")
			(
				(= curbuf (inq_buffer))
				(set_buffer (= newbuf (create_buffer vi_buf_prefix vi_buf_prefix TRUE)))
				(top_of_buffer)

				(if (!= (trim (read)) "")
					(
						(= mark_type (atoi (read)))
						(down)
						(drop_anchor mark_type)
						(end_of_buffer)

						(if (== mark_type NORMAL)
							(prev_char)
						)
						(copy)
					)
				;else
					(
						(error "Buffer is empty.")
						(delete_buffer newbuf)
					)
				)
				(set_buffer curbuf)
			)
		;else
			(inq_scrap NULL mark_type)
		)
		(rep_zero)
		(= num_pastes (+ 1 vi_rep_cnt))

		(if after
			(
				(save_position)

				(if (== mark_type LINE)
					(down)
				;else
					(right)
				)
			)
		)
		(while (-- num_pastes)
			(paste)
		)
		(if after
			(restore_position)
		)
		(vi_reset_cmd)
	)
)


;**	Sets the name of the current scrap buffer.  (The name must be
;**	a-z or 0-9.)

(macro vi_buffer
	(
		(int		ascii)

		(= ascii (& (pause "vi_buffer") 0xff))

		(if (|| (||
				(&& (>= ascii '0') (<= ascii '9'))
				(&& (>= ascii 'A') (<= ascii 'Z')))
				(&& (>= ascii 'a') (<= ascii 'z')))
			(sprintf vi_buf_prefix "scrap_%c" ascii)
		;else
			(error "Invalid scrap buffer name.")
		)
	)
)

;**	Indent or outdent according to the repeat count.

(macro vi_indent
	(
		(int		outdent)

		(save_position)
		(get_parm 0 outdent)

		(if outdent
			(vi_repeat "_vi_outdent")
		;else
			(vi_repeat "_vi_indent")
		)
		(restore_position)
	)
)

(macro _vi_indent
	(
		(extern	vi_ins_tab)

		(vi_white)
		(vi_ins_tab)
		(down)
	)
)

(macro _vi_outdent
	(
		(extern	back_tab)

		(vi_white)
		(drop_anchor 4)
		(back_tab)

		(if (inq_mark_size)
			(delete_block)
		;else
			(raise_anchor)
		)
		(down)
	)
)

;**	Gets the beginning or end of the next word forward, or the end
;**	of the next word backward, using either of two sets of
;**	delimiters.  The 3 parameters are TRUE/FALSE: word (which set
;**	of delimiters to use), backward (direction), and end (search
;**	for beginning or end of word).

(macro vi_word
	(
		(int		row
					col
					word
					backward
					end
		)
		(string	delim1
					delim2
					temp
		)
		(get_parm 0 word)
		(get_parm 1 backward)
		(get_parm 2 end)

		(if word
			(
				(= delim1 "[\t\n -/:-@\\[-`{|}\\~]")
				(= delim2 "[A-Za-z0-9]")
			)
		;else
			(
				(= delim1 "[ \t\n]")
				(= delim2 "[!-\\~]")
			)
		)

		;**	Swap the delimiters if we are looking for a word end.

		(if end
			(
				(= temp delim1)
				(= delim1 delim2)
				(= delim2 temp)
			)
		)
		(rep_zero)
		(= word vi_rep_cnt)
		(inq_position row col)

		(if backward
			(
				(if vi_prefix
					(
						(prev_char)
						(drop_anchor NORMAL)
					)
				)
				(prev_char)
			)
		;else
			(if vi_prefix
				(drop_anchor NORMAL)
			)
		)
		(if end
			(next_char)
		)
		(if backward
			(
				(while (>= (-- vi_rep_cnt) 0)
					(
						(search_back delim2)
						(search_back delim1)
					)
				)
				(next_char)
			)
		;else
			(
				(while (>= (-- vi_rep_cnt) 0)
					(
						(search_fwd delim1)
						(search_fwd delim2)
					)
				)
				(if end
					(prev_char)
				)
			)
		)

		(if vi_prefix
			(
				(if (&& (! backward) (! end))
					(prev_char)
				)
				(if (== vi_prefix YANK)
					(
						(vi_copy)
						(move_abs row col)
						(message "%d word(s) yanked." word)
					)
				;else
					(
						(vi_cut)
						(message "%d fewer word(s)." word)

						(if (== vi_prefix CHANGE)
							(vi_ins)
						)
					)
				)
			)
		)
		(vi_reset_cmd)
	)
)


;**	Search again in same direction for same string.

(macro vi_str_srch
	(
		(int		ret_code
					failed
		)
		(if (== _s_pat "")
			(error "No previous string search.")
		;else
			(
				(save_position)
				(rep_zero)

				(while (>= (-- vi_rep_cnt) 0)
					(
						(if _dir
							(
								(next_char)
								(= ret_code (search_fwd _s_pat 0))
							)
						;else
							(
								(prev_char)
								(= ret_code (search_back _s_pat 0))
							)
						)
						(if (<= ret_code 0)
							(
								(error "String <%s> not found." _s_pat)
								(++ failed)
								(break)
							)
						)
					)
				)
				(vi_reset_cmd)
				(restore_position failed)
			)
		)
	)
)

(macro vi_srch_fwd
	(
		(string	instr)

		(if (!= (= instr (vi_getstr '/')) "")
			(
				(= _dir FORWARD)
				(= _s_pat instr)
				(vi_str_srch)
			)
		)
	)
)

(macro vi_srch_back
	(
		(string	instr)

		(if (!= (= instr (vi_getstr '?')) "")
			(
				(= _dir BACKWARD)
				(= _s_pat instr)
				(vi_str_srch)
			)
		)
	)
)

;**	Search for the same pattern, but in the opposite direction,
;**	without affecting the global direction.

(macro vi_str_back
	(
		(= _dir (! _dir))
		(vi_str_srch)
		(= _dir (! _dir))
	)
)

;**	Prompt for a search string, VI-style.  If Alt-H is pressed, calls
;**	the context-sensitive help macro indirectly.

(macro vi_getstr
	(
		(int		inchr
					frstchr
		)
		(string	instr)

		(get_parm 0 frstchr)
		(message "%c" frstchr)

		(while TRUE
			(
				(= inchr (pause (inq_command)))

				(if (== (& inchr 0xff) 0)
					(call_registered_macro BAD_PROMPT_MACRO)
				;else
					(switch (int_to_key inchr)
						"<Esc>"
							(
								(returns "")
								(break)
							)
						"<Enter>"
							(
								(returns instr)
								(break)
							)
						"<Backspace>"
							(= instr (substr instr 1 (- (strlen instr) 1)))

						;default
						NULL
							(sprintf instr "%s%c" instr inchr)
					)
				)
				(message "%c%s" frstchr instr)
			)
		)
		(message "")
	)
)

(macro vi_search
	(
		(int		inchr
					prev
		)
		(get_parm 0 vi_chr_dir)
		(get_parm 1 prev)

		(if (== (& (= inchr (pause "vi_search")) 0xff) 0)
			(call_registered_macro BAD_KEY_MACRO)
		;else
			(
				(= vi_srch_chr (& inchr 0xff))
				(vi_chr_srch prev)
			)
		)
	)
)

(macro vi_chr_srch
	(
		(int		prev
					ret_code
					failed
		)
		(string	char_pat)

		(if (! vi_srch_chr)
			(error "No previous character search.")
		;else
			(
				(get_parm 0 prev)
				(sprintf char_pat "%c" vi_srch_chr)
				(save_position)
				(rep_zero)

				(while (>= (-- vi_rep_cnt) 0)
					(
						(if vi_chr_dir
							(
								(if prev
									(next_char)
								)
								(next_char)
								(= ret_code (search_fwd char_pat 0))

								(if prev
									(prev_char)
								)
							)
						;else
							(
								(if (! prev)
									(prev_char)
								)
								(prev_char)
								(= ret_code (search_back char_pat 0))

								(if prev
									(next_char)
								)
							)
						)
						(if (<= ret_code 0)
							(
								(error "Character <%c> not found." vi_srch_chr)
								(++ failed)
								(break)
							)
						)
					)
				)
				(vi_reset_cmd)
				(restore_position failed)
			)
		)
	)
)

(macro vi_chr_back
	(
		(= vi_chr_dir (! vi_chr_dir))
		(vi_chr_srch)
		(= vi_chr_dir (! vi_chr_dir))
	)
)


;**	Brace, parenthesis, and bracket matching.

(macro vi_match
	(
		(message "Finding match...")

		(switch (read 1)
			"("
				(vi_match_fwd TRUE "[()]" ")")
			"["
				(vi_match_fwd TRUE "[\\[\\]]" "]")
			"{"
				(vi_match_fwd TRUE "[{}]" "}")
			")"
				(vi_match_fwd FALSE "[()]" "(")
			"]"
				(vi_match_fwd FALSE "[\\[\\]]" "[")
			"}"
				(vi_match_fwd FALSE "[{}]" "{")

			;default
			NULL
				(error "Character not (), [] or {}.")
		)
		(vi_reset_cmd)
	)
)

(macro vi_match_fwd
	(
		(int		count
					forward
		)
		(string	pattern
					match
		)
		(get_parm 0 forward)
		(get_parm 1 pattern)
		(get_parm 2 match)
		(save_position)
		(= count 1)

		(while (> count 0)
			(if (<= (vi_match_dir forward pattern) 0)
				(= count -1)
			;else
				(if (== (read 1) match)
					(-- count)
				;else
					(++ count)
				)
			)
		)

		(if count
			(error "No matching %s." match)
		;else
			(message "")
		)
		(restore_position count)
	)
)

(macro vi_match_dir
	(
		(int		forward)
		(string	pattern)

		(get_parm 0 forward)
		(get_parm 1 pattern)

		(if forward
			(
				(next_char)
				(returns (search_fwd pattern TRUE TRUE FALSE))
			)
		;else
			(
				(prev_char)
				(returns (search_back pattern TRUE TRUE FALSE))
			)
		)
	)
)

;**	Execute an extended command, using the colon as the invoking
;**	character.  Note that the space is required as a delimiter for
;**	all commands that take parameters except the replace commands.
;**	The following extended commands are supported:
;**
;**	:a								toggle auto-indent mode
;**	:b								display buffer list
;**	:d								delete window
;**	:e <str>						add named file to buffer list
;**	:k <str>						delete a macro file from memory
;**	:l <str>						load a named macro file
;**	:m								move window (change size)
;**	:n								edit next file in buffer list
;**	:o								open new window
;**	:q								quit - no changes
;**	:q!							quit - cancel changes
;**	:r <str>						read file into current buffer
;**	:s/<str1>/<str2>/g		global replacement
;**	:s/<str1>/<str2>/p		prompted replacement
;**	:s/<str1>/<str2>/			single replacement
;**	:t	<str>						set tabs
;**	:wq							quit - write changes if any
;**	:w								write to file (update)
;**	:w <str>						write to non-existent file
;**	:w! <str>					overwrite file
;**	:x								exit all buffers
;**	:! <str>						execute DOS command
;**	:!!							repeat last DOS command (of 3 chars or more)

(macro vi_command
	(
		(int		pos
					malformed
		)
		(string	cmd_str
					cmd_arg
					replacement
		)
		(extern	buf_list
					display_file_name
					edit_next_buffer
		)
		(if (!= (= cmd_str (vi_getstr ':')) "")
			(
				;**	If there was a space in the string, break it up into
				;**	a command and an argument.

				(if (= pos (index cmd_str " "))
					(
						(= cmd_arg (substr cmd_str (+ pos 1)))
						(= cmd_str (substr cmd_str 1 (- pos 1)))
					)
				)

				;**	Branch on the command.

				(if (== (substr cmd_str 1 1) "s")
					(if (== (substr cmd_str 2 1) "/")
						(
							(= cmd_str (substr cmd_str 3))

							(if (= pos (index cmd_str "/"))
								(
									(= cmd_arg (substr cmd_str 1 (- pos 1)))
									(= cmd_str (substr cmd_str (+ pos 1)))

									(if (= pos (index cmd_str "/"))
										(
											(= replacement (substr cmd_str 1 (- pos 1)))

											(switch (substr cmd_str (+ pos 1) 1)

												"g"
													(
														(save_position)
														(top_of_buffer)
														(message "%d occurrences changed."
															(translate cmd_arg replacement TRUE))
														(restore_position)
													)

												"p"
													(message "%d occurrences changed."
														(translate cmd_arg replacement))

												""
													NULL

												;**	Default is to just replace once.

												NULL
													(
														(translate cmd_arg replacement FALSE)
														(message "")
													)
											)
										)
									;else
										(++ malformed)
									)
								)
							;else
								(++ malformed)
							)
						)
					;else
						(++ malformed)
					)
				;else
					(switch cmd_str

						;**	Toggle auto-indent mode.

						"a"
							(
								(= vi_ind_mode (! vi_ind_mode))
								(message "Automatic indenting %s."
									(if vi_ind_mode "on" "off"))
							)

						;**	List the buffers.

						"b"
							(buf_list)

						;**	Delete a window.

						"d"
							(delete_edge)

						;**	Edit a file.

						"e"
							(if (<= (edit_file cmd_arg) 0)
								(error "Can't edit %s." cmd_arg)
							;else
								(
									(inq_names cmd_arg)

									(if (exist cmd_arg)
										(display_file_name)
									;else
										(message "New file: %s." cmd_arg)
									)
								)
							)

						;**	Delete a macro file from memory.

						"k"
							(
								(delete_macro cmd_arg)
								(message "")
							)

						;**	Load a macro file.

						"l"
							(
								(load_macro cmd_arg)
								(message "")
							)

						;**	Resize a window.

						"m"
							(move_edge)

						;**	Change to the next buffer.

						"n"
							(edit_next_buffer)

						;**	Open another window.

						"o"
							(create_edge)

						;**	Abandon a buffer.

						"q"
							(if (inq_modified)
								(error "File has been modified.")
							;else
								(vi_quit)
							)

						"q!"
							(vi_quit)

						;**	Read in an existing file.

						"r"
							(if (<= (read_file cmd_arg) 0)
								(error "Can't read %s." cmd_arg)
							)

						;**	Set tabs.

						"t"
							(
								(execute_macro (+ "tabs " cmd_arg))
								(message "")
							)

						;**	Write a new file.

						"w"
							(
								(if (== cmd_arg "")
									(inq_names cmd_arg)
								)
								(if (exist cmd_arg)
									(error "File already exists.")
								;else
									(vi_write cmd_arg)
								)
							)

						;**	Write an existing file.

						"w!"
							(vi_write cmd_arg)

						;**	Write, then quit that buffer.

						"wq"
							(vi_write_quit cmd_arg)

						;**	Exit immediately.

						"x"
							(exit "y")

						;**	Run a DOS command, and save it for re-use if the
						;**	command succeeds and is more than 2 letters long.

						"!"
							(if (>= (dos cmd_arg TRUE) 0)
								(
									(if (> (strlen cmd_arg) 2)
										(= vi_dos_cmd cmd_arg)
									)
									(message "")
								)
							)

						"!!"
							(
								(message ":! %s" vi_dos_cmd)
								(dos vi_dos_cmd TRUE)
								(message "")
							)

						;**	Default

						NULL
							(
								(error "Unknown command: %s." cmd_str)
								(call_registered_macro BAD_KEY_MACRO)
							)
					)
				)
				(if malformed
					(error "Invalid replace command.")
				)
			)
		)
	)
)


;**	Exit a buffer at a time (without saving).  On the last buffer,
;**	exit BRIEF.

(macro vi_quit
	(
		(int		new_buf
					cur_buf
		)
		(string	new_name)

		(if (== (= cur_buf (inq_buffer)) (= new_buf (next_buffer)))
			(exit "y")
		;else
			(
				(set_buffer new_buf)
				(inq_names new_name NULL)
				(set_buffer cur_buf)
				(edit_file new_name)

				(if (! (inq_views cur_buf))
					(delete_buffer cur_buf)
				)
			)
		)
	)
)

;**	If the buffer is modified, tries to write it.  (Changes the
;**	buffer name before writing, if a parameter is passed.)
;**	Returns FALSE if the write fails, TRUE otherwise.

(macro vi_write
	(
		(string	pathname)

		(if (inq_modified)
			(
				(get_parm 0 pathname)

				(if (== pathname "")
					(inq_names pathname)
				)
				(if (|| (<= (output_file pathname) 0)
					(<= (write_buffer) 0))
					(
						(error "Can't write %s." pathname)
						(returns FALSE)
					)
				;else
					(returns TRUE)
				)
			)
		;else
			(
				(error "File has not been modified.")
				(returns TRUE)
			)
		)
	)
)

;**	Writes the current buffer, then quits it.

(macro vi_write_quit
	(
		(string	filename)

		(get_parm 0 filename)

		(if (vi_write filename)
			(vi_quit)
		)
	)
)
