/**********************************************************************/
/*                                                                    */
/*	CRISP - Programmable editor                                   */
/*	===========================                                   */
/*                                                                    */
/*  File:          crisp.cr                                           */
/*  Author:        P. D. Fox                                          */
/*  Created:       11 Apr 1991                     		      */
/*                                                                    */
/*  Copyright (c) 1990, 1991 Paul Fox                                 */
/*                All Rights Reserved.                                */
/*                                                                    */
/*                                                                    */
/*--------------------------------------------------------------------*/
/*  Description:  Crisp startup macros                                */
/*                                                                    */
/*   This  file  contains  a  lot  of  garbage  which doesn't really  */
/*   belong here and will eventually get moved out.		      */
/**********************************************************************/

# include	"crisp.h"

# define	INIT_FILE	".crinit"

/**********************************************************************/
/*   Following  variables  used  by  the search/translate macros and  */
/*   are  needed  by the state restore code, so we put them here for  */
/*   safe keeping.						      */
/**********************************************************************/
string	search__pattern;
string	translate__pattern;
string	translate__replacement;
/**********************************************************************/
/*   Macros  should  set  this  variable  to  TRUE  if they change a  */
/*   property.  If  this  flag  is  FALSE  on exit, then the .crinit  */
/*   file  will  not  be  re-written  and your personal changes will  */
/*   not be saved.						      */
/**********************************************************************/
int	properties_changed = FALSE;
/**********************************************************************/
/*   The  following  list  is  a list of all the objects we can save  */
/*   and   restore.   For  each  property  listed,  a  macro  called  */
/*   'get_OBJECT'   and   'set_OBJECT'   should   be  defined  which  */
/*   understands how to get and set the attribute.		      */
/*								      */
/*   Be  careful  when  adding  your  own  definitions -- you really  */
/*   want  to  avoid  autoloading  macros  which  aren't going to be  */
/*   needed most of the time.					      */
/**********************************************************************/
list properties = {
	"autoindent",
	"autosave",
	"colors",
	"hard_tabs",
	"syntax",
	"tabs"
	
/*	
	"autowrap",
	"case_sensitive",
	"justification",
	"margin",*/
	};

int	top_keyboard;
string	CRISP_OPSYS = "UNIX",
	CRISP_DELIM = "/",
	CRISP_SLASH = "/";
list	kbd_labels;
string	last_file_edited = "";
int	popup_level;

/**********************************************************************/
/*   The  following  macro  is  called on startup and is responsible  */
/*   for  setting  up  the initial environment. In addition, it sets  */
/*   up  the  following global variables which are used by the other  */
/*   macros  to  try  and  ensure  some  form of portability between  */
/*   operating systems.						      */
/*
/*	string 	CRISP_OPSYS
/*		This contains the string:
/*			VMS	- if running under VMS
/*			UNIX	- if running under any Unix variant.
/*			DOS	- if running under OS/2 or DOS
/*
/*	string	CRISP_DELIM
/*		This contains a string which can be used to concatenate a
/*		directory name and a filename. This string can be used
/*		for constructing filenames.
/*		Under VMS this is null; under Unix it is "/".
/*
/* string	CRISP_SLASH
/*		This contains the character used to delimit a directory
/*		name and a file name. This string can be used for breaking
/*		apart file-names.
/*		Under VMS this is "]"; under Unix it is "/".
/************************************************************************/
void
crisp()
{
	int	i, len;
	list	suf_list;
	string	kbd, term;

	top_keyboard = inq_keyboard();
	assign_to_key("^A", "extra");
	assign_to_key("^B", "set_bottom_of_window");
	assign_to_key("^C", "set_center_of_window");
	assign_to_key("^E", "nlang");
	assign_to_key("^F", "objects format_block");
	assign_to_key("^G", "objects routines");
	assign_to_key("^H", "backspace");
	assign_to_key("^I", "insert_tab");
	assign_to_key("^K", "objects delete_word_left");
	assign_to_key("^L", "objects delete_word_right");
	assign_to_key("^N", "edit_next_buffer");
	assign_to_key("^O", "options");
	assign_to_key("^P", "previous_edited_buffer");
	assign_to_key("^R", "repeat");
	assign_to_key("^T", "set_top_of_window");
	assign_to_key("^U", "redo");
	assign_to_key("^W", "set_backup");
	assign_to_key("^Z", "zoom");
	assign_to_key("^]", "tag_function");
	assign_to_key("^^", "find_matching_brace");
	assign_to_key("#127", "delete_character");
	assign_to_key("<Alt-B>", "buffer_list 1");
	assign_to_key("<Alt-E>", "edit__file");
	assign_to_key("<Alt-G>", "goto__line");
	assign_to_key("<Alt-H>", "help");
	assign_to_key("<Alt-N>", "edit_next_buffer");
	assign_to_key("<Alt-P>", "previous_alpha_buffer");
	assign_to_key("<Alt-S>", "search__fwd");
	assign_to_key("<Alt-T>", "translate__fwd");
	assign_to_key("<Alt-Y>", "search__back");
	assign_to_key("<F5>", "search__fwd");
	assign_to_key("<F6>", "translate__fwd");
	assign_to_key("<Alt-F5>", "search__back");
	assign_to_key("<Alt-F6>", "translate__back");
	assign_to_key("<Alt-F7>", "keylib");
	assign_to_key("<Alt-F10>", "load");
	assign_to_key("<Alt-End>", "goto_right_edge");
	assign_to_key("<Alt-Home>", "goto_left_edge");
	assign_to_key("<Alt-minus>", "previous_alpha_buffer");
	assign_to_key("<Ctrl-F5>", "toggle_re_case");
	assign_to_key("<Ctrl-F6>", "toggle_re");
	assign_to_key("<Ctrl-Left-Arrow>", "objects word_left");
	assign_to_key("<Ctrl-Right-Arrow>", "objects word_right");
	assign_to_key("<Ctrl-minus>", "delete_curr_buffer");
	assign_to_key("<Del>", "delete_character");
	assign_to_key("<End>", "end");
	assign_to_key("<Home>", "home");
	assign_to_key("<Keypad-5>", "search_next");
	assign_to_key("<Shift-F5>", "search_next");
	assign_to_key("<Shift-F6>", "translate_again");
	assign_to_key("<Shift-F10>", "load");
	assign_to_key("<Shift-Tab>", "insert_backtab");
	assign_to_key("<PrtSc>", "print");
	assign_to_key("<Keypad-Scroll>", "scroll");
	/***********************************************/
	/*   These  two  keys  append the data to the  */
	/*   scrap.				       */
	/***********************************************/
	assign_to_key("<Shift-Keypad-plus>", "copy 1");
	assign_to_key("<Shift-Keypad-minus>", "cut 1");
	/***********************************************/
	/*   These handle named scrap buffers.	       */
	/***********************************************/
	assign_to_key("<Ctrl-Keypad-plus>", "copy_named_scrap");
	assign_to_key("<Ctrl-Keypad-minus>", "cut_named_scrap");
	assign_to_key("<Ctrl-Ins>", "paste_named_scrap");
        /***********************************************/
        /*   Find  out  what  operating system we are  */
        /*   on.  We  do  this  by  testing  for  the  */
        /*   existence  of  files  that  are peculiar  */
        /*   to  the  operating  systems. These tests  */
        /*   may  get  the  wrong files in which case  */
        /*   you  may  need  to  tinker with them for  */
        /*   best  effect.  The  purpose  here  is to  */
        /*   have  a  global  variable  that  can  be  */
        /*   tested   in   the   macros   for  system  */
        /*   dependent   actions.  For  example,  VMS  */
        /*   has  different  file  naming conventions  */
        /*   to  unix  which  can cause the macros to  */
        /*   fail.				       */
        /***********************************************/
	
	if (exist("sys$input")) {
		CRISP_OPSYS = "VMS";
		CRISP_DELIM = "";
		CRISP_SLASH = "]";
		}
	else if (exist("\\")) {
		CRISP_OPSYS = "DOS";
		CRISP_DELIM = "/";
		CRISP_SLASH = "/";
		}
	
	register_macro(REG_ALT_H, "prompt_help");

	/***********************************************/
	/*   Tell CRISP where to find macros.	       */
	/***********************************************/
	autoload("abbrev", "load_abbrev_file");
	autoload("autosave", "autosave_disable", "get_autosave");
	autoload("brace", "find_matching_brace");
	autoload("brief",
		"del", "dos", 
		"inq_brief_level");
	autoload("c", "hier");
	autoload("compile",
		"set_load", "get_load", "load", "errors", "make",
		"lint", "default_next_error", "default_previous_error");
	autoload("copyr",
		"ccopyr",
		"fn");
	autoload("core",
		"_fatal_error"); 
	autoload("debug", "eval", "vars");
	autoload("extra", "edit_again", "make_writeable");
	autoload("feature", "select_feature");
	autoload("objects",
		"objects", 
		"shift_left", "shift_right", 
		"lshift", "shiftl", "rshift", "shiftr",
		"c_routines", "h_routines",
		"m_routines", "mm_routines", "select_routine");
	autoload("help",
		"help", "help_display", "explain", "help_command_summary",
		"cshelp");
	autoload("history",
		"_prompt_begin", "_prompt_end", "prompt_help");
	autoload("man", "apropos");
	autoload("misc",
		"delete_curr_buffer",
		"goto__line", "redo", "_indent", "ansi",
		"display_file_name", "end", "home", "noundo",
		"insert_tab", "insert_backtab", "force_input",
		"previous_tab", "quote", "repeat", "join_line", 
		"edit_next_buffer", "edit__file", "previous_edited_buffer",
		"previous_alpha_buffer", "set_fs",
		"delete_character", "delete_blank_lines",
		"print", "get_print");
	autoload("options",
		"options", "echo_line_options");
	autoload("region",
		"copy", "cut", "paste", "block_delete", "block_lower_case",
		"block_upper_case", "sum");
	autoload("remember", "remember", "keylib", "km_load", "km_path",
		"compl_keylib");
	autoload("restore", "_startup_complete", "save_state");
	autoload("scrap", "copy_named_scrap", 
		"cut_named_scrap", "paste_named_scrap");
	autoload("search",
		"i_search",
		"toggle_re", "toggle_re_case", "translate_again",
		"get_case_sensitive", "translate__back",
		"translate__fwd", "search__fwd", "search__back", "search_next",
		"search_prev", "search_options", "search_hilite");
	autoload("select",
		"field_list", "sized_window", "select_list", 
		"select_slim_list", "select_file",
		"select_buffer", "buffer_list");
	autoload("shell",
		"sh", "csh", "ksh", "bash", "create_shell");
	autoload("tags",
		"mtags", "tag", "tags", "tag_function");
	autoload("telnet",
		"rlogin");
	autoload("text",
		"grep", "spell", "wc");
	autoload("unix",
		"perform_unix_command");
	autoload("window",
		"goto_left_edge", "goto_right_edge",
		"set_top_of_window", "set_bottom_of_window",
		"set_center_of_window");
	autoload("wp",
		"autowrap",
		"center",
		"get_autowrap",
		"get_justification",
		"wp_options", 
		"h_format_block", 
		"cr_format_block",
		"y_format_block",
		"c_format_block",
		"default_format_block", "margin", "get_margin");
	autoload("view", "literal");
	/*---------------------------------------------------------*/
	/*    Find   out   what   terminal   type  we  are,  and   *
	/*    initialise   the   terminal   characteristics  for   *
	/*    CRISP.  We  do  this  by  first seeing if BTERM is   *
	/*    set.  If  it  is, then we load tty/$BTERM; if not,   *
	/*    we   use   TERM,  and  see  if  tty/$TERM  exists.   *
	/*    Otherwise,  we  default  to  tty.m  If  the  BTERM   *
	/*    environment    variable    is    of    the   form:   *
	/*    type-type1-type2,  then  we  load  tty/type.m  and   *
	/*    execute  macros  'type1',  'type2', ... This is to   *
	/*    avoid  exceeding  the  14 character filename limit   *
	/*    on  Sys  V,  and also to keep terminal definitions   *
	/*    which are similar in the same tty file.		   *
	/*---------------------------------------------------------*/
	if (display_mode() & DC_WINDOW) {
		int	maj, min, edit;
		string	buf, buf1;
		version(maj, min, edit);
		sprintf(buf, "Crisp v%d.%d%c", maj, min, edit);
		sprintf(buf1, "v%d.%d%c", maj, min, edit);
		set_wm_name(buf, buf1);
		len = 0;
		set_term_features(
			NULL,		/* Sequence to clear 'n' spaces.	    */
			NULL,		/* Sequence to print characters with top    */
					/* bit set.				    */
			NULL,		/* Insert-mode cursor.			    */
			NULL,		/* Overwrite-mode cursor.		    */
			NULL,		/* Insert-mode cursor (on virtual space).   */
			NULL,		/* Overwrite-mode cursor (on virtual space).*/
			NULL,		/* Print ESCAPE character graphically.	    */
			NULL,	  	/* Escape sequence to repeat last character.*/
			NULL,	  	/* ESC [0m resets color.		    */
			TRUE	  	/* Terminal supports color.		    */
			);
		/***********************************************/
		/*   Load up the mouse macros.		       */
		/***********************************************/
		load_macro("mouse");
		define_mouse_buttons();
		}
	else {
		term = getenv("BTERM");
		if (term == "")
			term = lower(getenv("TERM"));
		if (term == "")
			load_macro("tty/tty");
		else {
			suf_list = split(term, "-");
			if (!load_macro("tty/" + suf_list[0]))
				load_macro("tty/tty");
			}
		len = length_of_list(suf_list);
		}
	for (i = 0; ++i < len; )
		execute_macro(suf_list[i]);

	echo_line(0x3f);
	display_windows(1);
	/***********************************************/
	/*   Load  up  any  options from the defaults  */
	/*   file.				       */
	/***********************************************/
	init_load();
	refresh();
	/*----------------------------------------
	/*  See if this guy has a keyboard description
	/*  environment variable.
	/*----------------------------------------*/
	kbd = lower(getenv("BKBD"));
	if (kbd != "")
		load_macro("kbd/" + kbd);
}
void
shell_pop(string command)
{
	int	curwin = inq_window();
	int	curbuf = inq_buffer();
	int	buf = create_buffer("Shell Pop-Up", NULL, 1);
	int	line, col;

	create_window(55, 8, 77, 2);
	attach_buffer(buf);
	connect();
	insert(command + "\n");
	inq_position(line, col);
	set_process_position(line, col);
	insert_process(command + "\n");
	refresh();
	/*----------------------------------------
	/*   Wait for process to exit.
	/*----------------------------------------*/
	wait();
	delete_buffer(buf);
	delete_window();
	set_buffer(curbuf);
	set_window(curwin);
}
void
clear_buffer()
{
	top_of_buffer();
	drop_anchor();
	end_of_buffer();
	delete_block();
}

/**********************************************************************/
/*   Following  macro  used  for setting arrow keys to standard ANSI  */
/*   sequences.							      */
/**********************************************************************/
void
ansi_arrows()
{
	set_term_keyboard(
		KEY_DOWN, "[B",
		KEY_UP, "[A",
		KEY_RIGHT, "[C",
		KEY_LEFT, "[D");
}
/**********************************************************************/
/*   Macro  called  when  we  edit  a new file. We use it to set the  */
/*   Tab stops.							      */
/**********************************************************************/
list	tab_settings = {
	"m",		4,
	"cb",		4,
	"default",	9
	};
string	saved_tabs = ".m=4 .cb=4 default=9";

void
_extension()
{	string	ext;
	int	i;

	inq_names(NULL, ext);
	ext = "^" + quote_regexp(ext) + "$";
	i = re_search(NULL, ext, tab_settings);
	if (i < 0)	
		i = re_search(NULL, "default", tab_settings);
	if (i >= 0)
		tabs(tab_settings[i+1]);
	else
		tabs(9);
}
/**********************************************************************/
/*   Macro  called  by  properties  macro  to  get  the  current tab  */
/*   settings so we can save it.				      */
/**********************************************************************/
string
get_tabs()
{	
	return saved_tabs;
}
void
set_tabs(string arg)
{
	saved_tabs = arg;
	tab_settings = split(arg, " =.", 1);
}
/**********************************************************************/
/*   Macro  called  on  startup  to  read  the  properties  file and  */
/*   restore the saved attributes.				      */
/**********************************************************************/
void
init_load()
{
	string	file;
	int	buf, curbuf;
	string	line, fn;
	int	i;
	
	if (!first_time())
		return;
	register_macro(REG_EXIT, "init_exit");
	/***********************************************/
	/*   Read properties file into a buffer.       */
	/***********************************************/
	if (getenv("HOME") == "")
		file = "~/" + INIT_FILE;
	else
		file = getenv("HOME") + "/" + INIT_FILE;

	/***********************************************/
	/*   If  init  file  doesn't exist, then this  */
	/*   is  probably  the  first time we've been  */
	/*   executed  so  set  the flag to cause the  */
	/*   file to be written away on exit.	       */
	/***********************************************/
	if (!exist(file)) {
		properties_changed = TRUE;
		return;
		}
	curbuf = inq_buffer();
	buf = create_buffer("-props-", file, TRUE);
	set_buffer(buf);
	
	properties = NULL;
	/***********************************************/
	/*   Process each line in turn.		       */
	/***********************************************/
	while ((line = compress(read(), TRUE)) != "") {
		down();
		/***********************************************/
		/*   Make sure we have a colon in the line.    */
		/***********************************************/
		i = index(line, ":");
		if (i <= 0)
			continue;
		fn = substr(line, 1, i - 1);
		properties += fn;
		/***********************************************/
		/*   Check  for  a  macro called 'set_THING'.  */
		/*   If  not  defined, just execute the macro  */
		/*   called 'THING'.			       */
		/***********************************************/
		if (inq_macro("set_" + fn))
			fn = "set_" + fn;
		sprintf(fn, "%s \"%s\"", fn, ltrim(substr(line, i+1)));
		execute_macro(fn);
		}
	/***********************************************/
	/*   Delete  our  property buffer and restore  */
	/*   the buffer we already had on startup.     */
	/***********************************************/
	set_buffer(curbuf);
	attach_buffer(curbuf);
	delete_buffer(buf);
}
/**********************************************************************/
/*   This  macro  is called on exit to save the properties which are  */
/*   defined by various macros.					      */
/**********************************************************************/
void
init_exit()
{

	int	i, len;
	string	s;
	string	file;
	int	buf, curbuf;
		
	if (getenv("HOME") == "")
		file = "~/" + INIT_FILE;
	else
		file = getenv("HOME") + "/" + INIT_FILE;
	
	/***********************************************/
	/*   If  buffer  already  loaded  it  must be  */
	/*   because  user  is  editing  it, so don't  */
	/*   bother to save anything.		       */
	/***********************************************/
	if (inq_buffer(file) > 0)
		return;
	curbuf = inq_buffer();
	buf = create_buffer("-props-", NULL, TRUE);
	set_buffer(buf);

	output_file(file);

	for (i = 0, len = length_of_list(properties); i < len; ) {
		s = properties[i++];
		if (inq_macro("get_" + s))
			insert(s + ": " + execute_macro("get_" + s) + "\n");
		else
			insert(s + ":\n");
		}
	write_buffer();
	
	set_buffer(curbuf);
	attach_buffer(curbuf);
	delete_buffer(buf);
	
}
string
get_hard_tabs()
{
	return use_tab_char(0) ? "yes" : "no";
}
void
set_hard_tabs(string arg)
{
	use_tab_char(arg);
}
string
get_syntax()
{
	return re_syntax() ? "unix" : "crisp";
}
void
set_syntax(string arg)
{
	if (arg == "unix")
		re_syntax(1);
	else
		re_syntax(0);
}
/**********************************************************************/
/*   List  of  all  available  colors  corresponding to the internal  */
/*   color codes.						      */
/**********************************************************************/
# define	ALL_COLORS	\
		"black", 		\
		"blue", 		\
		"green", 		\
		"cyan", 		\
		"red", 			\
		"magenta", 		\
		"brown", 		\
		"white", 		\
		"dark-grey",		\
		"light-blue", 		\
		"light-green", 		\
		"light-cyan",		\
		"light-red", 		\
		"light-magenta", 	\
		"light-yellow",		\
		"light-white"
list color_names = {ALL_COLORS};
string
get_colors()
{
	int	background, normal, selected, messages, errors, hi_bg, hi_fg;
	string	buf;
	get_color(background, normal, selected, messages, errors, hi_bg, hi_fg);
	
	sprintf(buf, "%s %s %s %s %s %s %s",
		color_names[background],
		color_names[normal],
		color_names[selected],
		color_names[messages],
		color_names[errors],
		color_names[hi_bg],
		color_names[hi_fg]);
	return buf;
}
void
set_colors(string arg)
{
	execute_macro("color " + arg);
}
int	autoindent_flag = TRUE;
void
autoindent()
{
	string	arg;
	
	get_parm(0, arg, "Turn autoindent on (y/n) ? ");
	if (upper(substr(arg, 1, 1)) == "Y") {
		autoindent_flag = TRUE;
		assign_to_key("<Enter>", "_indent");
		}
	else {
		assign_to_key("<Enter>", "self_insert");
		autoindent_flag = FALSE;
		}
}
void
set_autoindent(string arg)
{
	autoindent(arg);
}
string
get_autoindent()
{
	return autoindent_flag ? "yes" : "no";
}
/**********************************************************************/
/*   Awk-like sub macro.					      */
/**********************************************************************/
string
sub(string r, string s, string t)
{
	return re_translate(SF_UNIX, r, s, t);
}
/**********************************************************************/
/*   Awk-like gsub macro.					      */
/**********************************************************************/
string
gsub(string r, string s, string t)
{
	return re_translate(SF_GLOBAL | SF_UNIX, r, s, t);
}

/**********************************************************************/
/*   Overloaded  version  of  cd.  If called by user then set window  */
/*   title to current directory.				      */
/**********************************************************************/
string
cd()
{	string	__dir;

	if (inq_called() != "")
		return cd();
	if (get_parm(0, __dir) > 0) {
		cd(__dir);
		getwd(NULL, __dir);
		set_wm_name(__dir);
		}
	else
		cd();
}
int
write_buffer()
{
	int	ret,
		old_msg_level;

	if (inq_called() != "")
		return write_buffer(); 
		
	old_msg_level = inq_msg_level();
	if (inq_marked()) {
		set_msg_level(1);
		ret = write_block();
		}
	else {
		set_msg_level(0);
		ret = write_buffer();
		}
	set_msg_level(old_msg_level);
	return ret;
}

