/* ** BRIEF -- Basic Reconfigurable Interactive Editing Facility ** ** Written by Dave Nanian and Michael Strickman. */ /* ** misc.h: ** ** This file contains a number of miscellaneous BRIEF functions. */ #define REG_MARK 1 #define COL_MARK 2 #define LINE_MARK 3 #define NI_MARK 4 #define NOTHING 0 #define REPLACE 1 #define REMOVE 2 string add_to_path (string path, string name); void _col_cut (~int append); void _col_copy (~int append); void _col_paste (); void _col_delete (); int next_word (); int previous_word (); int _dir, // Direction of last search. _t_dir, // Direction of last translate. _reg_exp, // Use regular expressions? _block_search, // Restrict search to blocks? _check_warnings, // Recognize warning-only compiles? _background, // Perform compilations in the background? _package_buf; // Parsed language package information. string _s_pat, // Value of last search pattern. _t_pat, // Value of last translate pattern. _r_pat; // Value of last replacement pattern. /* ** _init: ** ** This macro defines the global variables used by the various BRIEF ** packages that may be restored by the restore macro. Placing the ** variables here ensures the macro isn't autoloaded when its variables ** are referenced. */ void _init () { _reg_exp = _dir = _t_dir = 1; } /* ** delete_char: ** ** This macro implements block-sensitive delete. */ replacement delete_char () { int block_type; if (inq_called () != "" || !(block_type = inq_marked ())) return (delete_char ()); else if (block_type == COL_MARK) _col_delete (); else { int old_msg_level = inq_msg_level (); set_msg_level (0); delete_block (); set_msg_level (old_msg_level); } } /* ** delete_next_word, delete_previous_word: ** ** These functions use the language-sensitive next_word and previous_word ** functions to perform word deletion. */ int delete_next_word () { int line; string line_end = trim (read ()); save_position (); drop_anchor (4); inq_position (line); if (next_word ()) { int after_line; inq_position (after_line); if (after_line != line && line_end != "") { restore_position (); raise_anchor (); delete_to_eol (); return (1); } else if (inq_marked ()) { delete_block (); return (1); } } restore_position (); raise_anchor (); returns (0); } int delete_previous_word () { save_position (); drop_anchor (4); if (previous_word () && inq_marked ()) { delete_block (); restore_position (0); returns (1); } else { restore_position (); raise_anchor (); returns (0); } } /* ** delete_to_bol: ** ** This routine deletes to the beginning of the line. */ int delete_to_bol (void) { drop_anchor (4); if (beginning_of_line ()) { delete_block (); returns (1); } else { raise_anchor (); returns (0); } } /* ** write_buffer: ** ** This replacement macro decides what to do when Alt-w is pressed. ** If a block is marked, then the "write_block" primitive is called. ** Otherwise, write_buffer is called to write out the entire buffer. */ replacement int write_buffer (...) { if (inq_called () != "") return (write_buffer ()); else { int old_msg_level = inq_msg_level (), ret_code; set_msg_level (0); if (inq_marked ()) ret_code = write_block (); else ret_code = write_buffer (); set_msg_level (old_msg_level); return (ret_code); } } /* ** write_and_exit: ** ** This command writes all buffers and exits BRIEF. */ void write_and_exit (void) { /* ** We set the message level to ensure that the informational ** messages displayed by exit ("w") can be read by the user. */ set_msg_level (0); exit ("w"); } /* ** load_keystroke_macro: ** ** This routine acts as a front-end to the playback function, prompting ** for the name of the keystrke macro to load. */ int load_keystroke_macro (~string) { int ret_code, old_msg_level = set_msg_level (0); string file_name; if (inq_keystroke_macro ()) ret_code = playback (); else if ((ret_code = get_parm (0, file_name, "Keystroke macro file: ")) > 0) if (file_name != "") ret_code = playback (file_name, NULL, 0); set_msg_level (old_msg_level); returns (ret_code); } /* ** save_keystroke_macro: ** ** This replacement for the base save_keystroke_macro displays the path ** that the macro is going to be saved to. Rather than save to the current ** directory as the built-in version does, the replacement cycles through ** the BPATH. */ replacement int save_keystroke_macro (~string) { if (inq_called () != "") returns (save_keystroke_macro ()); else { int old_msg_level = set_msg_level (0), num_keys, ret_code; /* ** First, we determine whether or not we should bother prompting. ** The normal save_keystroke_macro command won't even bother prompting ** if we're remembering, playing back, or if there's no macro. We do ** the same, but let the base function display the error message. */ if (inq_keystroke_macro (num_keys) || num_keys == 0) ret_code = save_keystroke_macro (); else { string prompt, bpath, file_name, curr_dir; int start_loc = 1, loc, len, max_width; /* ** Here, we display a prompt that should help people save ** keystroke macros. We loop through the BPATH environment ** variable, displaying the next directory every time ** is pressed on a blank line. This directory is the "base" ** directory to save to. */ inq_screen_size (NULL, max_width); max_width -= 32 + 23 + 4; if ((bpath = trim (ltrim (compress (inq_environment ("BPATH"))))) == "") bpath = ";/brief/macros"; do { if (loc = index (substr (bpath, start_loc), ";")) { curr_dir = trim (ltrim (substr (substr (bpath, start_loc), 1, loc - 1))); start_loc += loc; } else { curr_dir = trim (ltrim (substr (bpath, start_loc))); start_loc = 1; } if (curr_dir == "" || curr_dir == ".") getwd ("", curr_dir); if ((len = strlen (curr_dir)) <= max_width) sprintf (prompt, "Save keystrokes as [%s]: ", curr_dir); else { int over = len - max_width, mid = len / 2; sprintf (prompt, "Save keystrokes as [%s...%s]: ", substr (curr_dir, 1, mid - (over / 2) - 3 - (over % 2)), substr (curr_dir, mid + (over / 2) + 1)); } } while ((ret_code = get_parm (0, file_name, prompt)) && !strlen (file_name)); /* ** At this point, we take the "base" directory (from the BPATH) ** and the file name that the user specified, and put them together ** in an intelligent way. If a drive letter is specified, or if ** the path specified is absolute, we ignore the base and just use ** what the user specified. */ if (ret_code > 0) if (index (file_name, ":") || index ("/\\", substr (file_name, 1, 1))) ret_code = save_keystroke_macro (file_name); else ret_code = save_keystroke_macro (add_to_path (curr_dir, file_name)); } returns (ret_code); } } /* ** display_file_name: ** ** Displays the current buffer's file name on the status line. If the ** name is too large, as much of the end as will fit is displayed, with an ** ellipsis in the middle. */ void display_file_name () { #define FILE_PROMPT "File: " string file_name; int len, max_width; inq_names (file_name, NULL); inq_screen_size (NULL, max_width); if (inq_modified ()) file_name += "*"; len = strlen (file_name) + strlen( FILE_PROMPT ); // Length of string if ( len <= max_width) message ("%s%s", FILE_PROMPT, file_name); else { int over = len - max_width, mid = len / 2; message ("%s%s...%s", FILE_PROMPT, substr (file_name, 1, mid - (over / 2) - 3 - (over % 2)), substr (file_name, mid + (over / 2) + 1)); } } /* ** open_line: ** ** Adds a blank line after the current line, placing the cursor on the ** first column of the new line. The normal macro for the Enter key is ** used to perform the function after the end of line. */ void open_line () { end_of_line (); if (inq_assignment ("") == "self_insert" || inq_assignment ("") == "open_line") insert ("\n"); else execute_macro (inq_assignment ("")); } /* ** back_tab: ** ** Moves back to the previous tab stop. Deals with the appropriate ** special cases as well. */ void back_tab () { int col, start_col, start_dist = distance_to_tab (); inq_position (NULL, start_col); /* ** If we were on a tab stop to start with, we back up two ** characters. Otherwise, just one. */ move_rel (0, -1); if (distance_to_tab () < start_dist) move_rel (0, -1); while (distance_to_tab () != 1 && move_rel (0, -1)); /* ** If the last place we were was the first column, we just stay ** there. Also, if there were any one space tabs, we know that ** we should not move back to the original tab stop. */ inq_position (NULL, col); if (col != 1 && start_col - col != 1) move_rel (0, 1); } /* ** quote: ** ** This macro inserts the next character from the keyboard as-is. ** Non-ASCII keys, such as the Alt keys, are pushed back and executed ** as commands. */ void quote () { int value; while ((value = read_char ()) == -1); if (value & 0xff && ((value & 0xff) <= 127 || !(value & 0xff00))) insert ("%c", value); else push_back (value); } /* ** copy: ** ** This macro checks to see if a block is marked when the "copy" key ** is pressed. If so, the block is copied. If not, a single line is ** copied. */ replacement int copy (~int) { if (inq_called () != "") returns (copy ()); else { int block_type; if (block_type = inq_marked ()) { if (block_type == COL_MARK) { int append; get_parm (0, append); _col_copy (append); } else { int old_msg_level = inq_msg_level (); set_msg_level (0); copy (); set_msg_level (old_msg_level); } } else { drop_anchor (LINE_MARK); copy (); message ("Line copied to scrap."); } } } /* ** cut: ** ** This macro checks to see if a block is marked when the "cut" key ** is pressed. If so, the block is cut. If not, a single line is cut. */ replacement int cut (~int) { if (inq_called () != "") returns (cut ()); else { int block_type; if (block_type = inq_marked ()) { if (block_type == COL_MARK) { int append; get_parm (0, append); _col_cut (append); } else { int old_msg_level = inq_msg_level (); set_msg_level (0); cut (); set_msg_level (old_msg_level); } } else { drop_anchor (LINE_MARK); cut (); message ("Line cut to scrap."); } } } /* ** paste: ** ** This replacement macro checks to see if the current scrap is a column ** scrap buffer. If so, it calls the _col_paste routine. */ replacement int paste () { if (inq_called () != "") returns (paste ()); else { int scrap_type; inq_scrap (NULL, scrap_type); if (scrap_type == COL_MARK) _col_paste (); else { int old_msg_level = inq_msg_level (); set_msg_level (0); paste (); set_msg_level (old_msg_level); } } } /* ** _home/new_home: ** ** These two macros implement "triple-click" Home logic: ** ** The first time Home is pressed, it moves to the beginning of the ** line. ** ** The second time Home is pressed, it moves to the top of the screen. ** ** The third time Home is pressed, it moves to the top of the file. ** ** If any keys are pressed in between presses of Home, this sequence ** starts back at the beginning. */ void _home () { global int _new_home; if (inq_command () != "new_home") { _new_home = 0; beginning_of_line (); assign_to_key ("", "new_home"); push_back (key_to_int ("")); } else if (++_new_home == 1) top_of_window (); else { top_of_buffer (); set_top_left (1, 1); } } void new_home () { assign_to_key ("", "_home"); } /* ** _end/new_end: ** ** These two macros implement "triple-click" End logic: ** ** The first time End is pressed, it moves to the end of the line. ** ** The second time End is pressed, it moves to the end of the last line ** on the screen. ** ** The third time End is pressed, it moves to the end of the file. ** ** If any keys are pressed in between presses of End, this sequence ** starts back at the beginning. */ void _end () { global int _new_end; if (inq_command () != "new_end") { _new_end = 0; end_of_line (); assign_to_key ("", "new_end"); push_back (key_to_int ("")); } else { int col, win_cols, shift; inq_window_size (NULL, win_cols, shift); if (++_new_end == 1) { end_of_window (); end_of_line (); } else end_of_buffer (); inq_position (NULL, col); if (shift && col <= win_cols) { beginning_of_line (); refresh (); end_of_line (); } else if (_new_end == 1) refresh (); } } void new_end () { assign_to_key ("", "_end"); } /* ** add_to_path: ** ** This simple routine adds a filename to a path and returns the ** result. */ string add_to_path (string path, string name) { if (strlen (path) && !index ("/\\:", substr (path, strlen (path)))) path += "/"; path += name; returns (path); } /* ** escape_re: ** ** This function changes a string with regular expression characters in ** it to one without, and returns the result. Optionally, a different group ** of "regular expression" characters can be passed. */ string escape_re (string original, ~string) { int curr_pos, original_len; string bad_chars, escaped, curr_char; if (!get_parm (1, bad_chars)) bad_chars = "?*@+\\|%${}[]<>"; original_len = strlen (original); while (original_len--) escaped += (index (bad_chars, curr_char = substr (original, ++curr_pos, 1)) ? "\\" : "") + curr_char; returns (escaped); } /* ** search_path: ** ** This routine searches a path, such as the PATH or BPATH environment ** variable, for the specified file. It returns the null string if the ** file wasn't found, or the full path name if it was. */ string search_path (string path, string filename) { int semi_loc; string curr_name; /* ** Get the directory list to check and append a semicolon to it. ** This means we will always check the current directory last ** unless we are told to check it sooner. */ path += ";"; /* ** Search all the directories for the given file name. As soon ** as we find the file, append the name of the file to the ** given directory and return the full name. */ while (semi_loc = index (path, ";")) { curr_name = add_to_path (substr (path, 1, semi_loc - 1), filename); /* ** If the file exists in the given directory, we return ** the full name without bothering to parse anything else. */ if (exist (curr_name)) return (curr_name); path = substr (path, semi_loc + 1); } returns (""); } /* ** gen_block: ** ** This routine goes through the current block and passes the information ** from the block to a given routine. That routine performs an operation ** on the string, and then tells gen_block to replace, remove or ignore the ** data. ** ** Note that no line boundaries are ever removed by this routine. Also, ** the anchor is left as is. */ void gen_block (string macro_name, string msg) { string before, after; int start_line, start_col, end_line, end_col, curr_line, num_lines, num_chars, do_upper, lines_per_step, curr_step, scale_factor, type, action; message (msg + "..."); save_position (); if (!inq_marked ()) mark (LINE_MARK); type = inq_marked (start_line, start_col, end_line, end_col); num_lines = (end_line - start_line) + 1; curr_line = start_line; if (above (num_lines, 100)) { if (above (num_lines, 32767)) scale_factor = 1; else scale_factor = 32767 / num_lines; lines_per_step = num_lines / 100; } else { scale_factor = 100; lines_per_step = 1; } move_abs (start_line, start_col); while (below_eq (curr_line, end_line)) { save_position (); drop_anchor (); move_abs (0, curr_line == end_line || type == COL_MARK ? end_col : 2 * inq_line_length ()); num_chars = inq_mark_size (); raise_anchor (); restore_position (); after = before = read (num_chars); action = execute_macro (macro_name, after); if (action == REMOVE || after != before) { int test1_col, test2_col; inq_position (NULL, test1_col); if (action == REPLACE && prev_char ()) { next_char (); inq_position (NULL, test2_col); if (test2_col > test1_col) prev_char (); } if (index (after, "\n")) { after = substr (after, 1, strlen (after) - 1); delete_to_eol (); } else { drop_anchor (); move_abs (0, end_col); delete_block (); } if (action == REPLACE) insert (after); } move_abs (++curr_line, type == COL_MARK ? start_col : 1); if (++curr_step == lines_per_step) { curr_step = 0; message ("%s, %d%% complete...", msg, (100 * (((curr_line - start_line) * scale_factor) / num_lines)) / scale_factor); } } restore_position (); } void toupper () { gen_block ("_do_upper", "Uppercasing block"); raise_anchor (); message ("Uppercasing complete."); } int _do_upper (string line) { put_parm (0, upper (line)); returns (REPLACE); } void tolower () { gen_block ("_do_lower", "Lowercasing block"); raise_anchor (); message ("Lowercasing complete."); } int _do_lower (string line) { put_parm (0, lower (line)); returns (REPLACE); }