/* ** BRIEF -- Basic Reconfigurable Interactive Editing Facility ** ** Written by Dave Nanian and Michael Strickman. */ /* ** prompt.cb: ** ** This file contains routines that deal with prompt responses. This ** includes the prompt history function and file name completion. ** ** The history macro keeps track of all the >1 character responses ** made to prompts throughout the system. They can then be retrieved ** using the up and down arrow keys at the prompt. ** ** The complete macro expands file names at the edit_file, read_file, ** load_macro, and delete_macro prompts (as well as prompts that a ** user has added to the list). If a file name is ambiguous, it ** pops up a menu of choices. */ #include "dialog.h" #include "win_ctrl.h" #define DIRECTORY 0x10 /* ** When doing file completion, BRIEF sorts the directory until ** MAX_SORT_SECONDS has elapsed. You can change this value to force BRIEF ** to sort the entire directory (a high value), or to not sort at all (zero). */ #define MAX_SORT_SECONDS 3 /* ** _init: ** ** When history is loaded, this macro creates the buffer that retains the ** history information. Note that this is a non-file system buffer. */ string _last_response; // The last thing a user typed at a prompt. int _history_buf; // The buffer that contains the history info. string pre_func; // The previous mouse menu function void _init () { _history_buf = create_buffer ("History", NULL, 1); register_macro (5, "history_exit"); } /* ** history_exit: ** ** This macro zeros the history buffer setting so, once exit has been ** chosen (and confirmed), no further history processing is done. */ void history_exit () { _history_buf = 0; } /* ** _prompt_begin: ** ** This macro is called just before a prompt is displayed to the user. ** It inserts the current prompt information to keep things straight, and ** then attempts to locate previous history information in the buffer. If ** this information is found, the cursor is left there. If not, the cursor ** is left at the top of the file. */ string _prompt_begin (string) { if (_history_buf) { int old_buf = set_buffer (_history_buf), old_msg_level = set_msg_level (3); string curr_prompt; save_position (); top_of_buffer (); get_parm (0, curr_prompt); insert (curr_prompt); beginning_of_line (); /* ** This pattern is used throughout the history processing code ** as it shouldn't occur to often in a real prompt. In the first ** set of brackets is the name of the command, and in the second, ** the text of the prompt. Each prompt of each command has a ** distinct 10 response history. */ search_fwd ("[" + inq_command () + "]::[" + curr_prompt + "]", 0); set_buffer (old_buf); set_msg_level (old_msg_level); } returns (inq_cmd_line ()); } /* ** _insert_sorted: ** ** This macro inserts a filename into a list using a binary insertion. ** It needs the upper and lower bounds of the area to search in. ** ** Since directories are often in sorted order, we do an initial ** probe at the bottom of the list to see if we can skip the insertion. ** Although this adds an additional probe in the non-sorted case, it ** significantly speeds up the sorted case. */ void _insert_sorted (string to_insert, int top, int bottom) { if (top <= bottom) { string line_text; move_abs (bottom, 1); line_text = read (); if (line_text != "\n" && to_insert < line_text) { int middle_line; while (top < bottom) { middle_line = top + ((bottom - top) / 2); move_abs (middle_line, 1); if (to_insert < read ()) bottom = middle_line - 1; else top = middle_line + 1; } move_abs (top, 1); line_text = read (); if (line_text != "\n" && to_insert > line_text) down (); } else if (line_text != "\n") down (); } else move_abs (top, 1); insert (to_insert + "\n"); } /* ** _insert_unsorted: ** ** This routine inserts the passed item at the bottom of the list. We use ** this routine after a certain number of seconds has elapsed to ensure that ** things don't take too long. */ void _insert_unsorted (string to_insert, int top, int bottom) { if (top > bottom) move_abs (top, 1); else move_abs (bottom + 1, 1); insert (to_insert + "\n"); } /* ** _complete_edit_file, _complete_read_file, ** _complete_load_macro, _complete_delete_macro: ** ** These four macros provide "restricted" file name completion for ** four common BRIEF functions. Each restricts the displayed choices ** appropriately for the type of function. ** ** Note that these functions assume that you'll never edit an extensionless ** file called "exe", "com", "cm" or "obj". */ _complete_edit_file (string file) { switch (substr (file, rindex (file, ".") + 1)) { case "exe": case "com": case "cm": case "obj": returns (""); default: returns (file); } } _complete_read_file (string file) { returns (_complete_edit_file (file)); } _complete_load_macro (string file) { if (substr (file, rindex (file, ".") + 1) == "cm") returns (file); else returns (""); } _complete_delete_macro (string file) { returns (_complete_load_macro (file)); } /* ** _bad_key: ** ** This macro keeps tabs on the "illegal" keys pressed at the prompt. If ** the up or down arrows are pressed, we jump into action, moving around in ** the history buffer. If the bad key is , we try to expand the filename. */ _bad_key () { int key_pressed = read_char (), old_buf = inq_buffer (), old_msg_level = inq_msg_level (); string command = inq_command (); /* ** If the bad key is a Tab, we check to see if we can complete ** the file name on the command line (if, indeed, the command ** is one of the ones we care about). */ if (key_pressed == key_to_int ("")) { if (!inq_macro ("_complete_" + command)) { push_back (key_pressed); returns (0); } else { int loc, file_list_buf, attribute, fwd_slash, start_time, curr_time, hours, minutes, num_directories, num_files; string file_spec, found_file, path; /* ** If BRIEF is using forward slashes in the path name rather ** than backslashes, or the user used forward slashes in his ** current prompt response, we use forward slashes too. */ inq_names (file_spec); fwd_slash = index (file_spec, "/") || index (inq_cmd_line (), "/"); /* ** First, we separate the file_spec to be expanded into ** path and file parts. */ file_spec = trim (inq_cmd_line ()); if (loc = search_string ("[:/\\\\][~:/\\\\]@>", file_spec, NULL, TRUE)) { path = substr (file_spec, 1, loc); file_spec = substr (file_spec, loc + 1); } /* ** If there is no file name on the command line, or if ** there's just a base part with no extension, we look ** for files matching the base plus "*.*". Otherwise, we ** add a single "*". (If the user put wildcard ** characters into the file name, we just expand his ** specification.) ** ** If the base part is eight characters, we just add ** ".*": OS/2 doesn't truncate match requests, so xxxxxxxx*.* ** won't match names beginning with xxxxxxxx (it's looking for ** more characters). */ if (!search_string ("[?*]", file_spec, NULL, TRUE)) if (rindex (file_spec, ".")) file_spec += "*"; else file_spec += strlen (file_spec) >= 8 ? ".*" : "*.*"; /* ** Get a list of matching files into a buffer. Don't count ** directories. When we're done, found_file contains the ** last name matched, and the cursor is one line past the last ** file name. */ set_buffer (file_list_buf = create_buffer ("Files", NULL, TRUE)); delete_line (); file_pattern (file_spec = (path + file_spec)); time (hours, minutes, start_time); start_time += (hours * 60) + (minutes * 60); while (find_file (found_file, NULL, NULL, NULL, attribute)) if (found_file != ".") { found_file = lower (found_file); time (hours, minutes, curr_time); curr_time += (hours * 60) + (minutes * 60); if (attribute & DIRECTORY) { if (curr_time - start_time > MAX_SORT_SECONDS) _insert_unsorted (found_file + (fwd_slash ? "/" : "\\"), 1, num_directories); else _insert_sorted (found_file + (fwd_slash ? "/" : "\\"), 1, num_directories); num_directories++; } else if ((found_file = execute_macro ("_complete_" + command, found_file, file_spec)) != "") { if (curr_time - start_time > MAX_SORT_SECONDS) _insert_unsorted (found_file, num_directories + 1, num_directories + num_files); else _insert_sorted (found_file, num_directories + 1, num_directories + num_files); num_files++; } } num_files += num_directories; /* ** If we found exactly one match, update the command line. ** ** If we found none, beep, but don't change the display. ** ** If we found multiple matches, let the user choose some ** to return. If he declines to choose, don't change the ** command line. Note that we sometimes push back an ** to prevent the command line being highlighted, sometimes ** an to force the command to take the argument. ** ** If the end result is a directory specifier, we push back ** a to force expansion on the directory. */ if (num_files) { if (num_files == 1) { top_of_buffer (); path += trim (read ()); push_back (key_to_int ("")); } else { int ty, by, multifile_keyboard, multifile = inq_macro ("_multifile_" + command); /* ** Compute the screen size of the menu. */ inq_screen_size (by); by -= 4; if ((ty = by - (num_files + 1)) <= 0) ty = 1; /* ** Turn messages off so we don't overwrite the ** current prompt. Then pop up the file list. ** _process_menu will return the selected menu ** item in found_file. ** ** Note that if there's a multifile macro for this ** command, we set up a local keyboard that allows ** multiple file selection. */ if (multifile) { keyboard_push (); assign_to_key ("", "_complete_select"); assign_to_key ("*", "_complete_select_all"); multifile_keyboard = inq_keyboard (); keyboard_pop (TRUE); use_local_keyboard (multifile_keyboard); } set_msg_level (3); set_buffer (old_buf); _process_menu (15, by, 30, ty, NULL, "", NULL, file_list_buf, "_complete_action", TRUE, NULL, found_file); set_msg_level (old_msg_level); set_buffer (file_list_buf); if (multifile) { set_buffer (old_buf); keyboard_push (multifile_keyboard); keyboard_pop (); set_buffer (file_list_buf); } found_file = trim (found_file); if (found_file != "") { if (search_string ("[/\\\\]", found_file, NULL, TRUE)) { push_back (key_to_int ("")); push_back (-1); push_back (key_to_int ("")); } else { if (substr (found_file, 1, 1) == ">") found_file = substr (found_file, 2); num_files = 0; /* ** If there's a multifile macro for this command, ** then we do some more processing. */ if (multifile) { delete_line (); top_of_buffer (); while (!inq_position ()) { if (read (1) != ">") delete_line (); else { num_files++; delete_char (); down (); } } if (num_files > 0) execute_macro ("_multifile_" + command, file_list_buf, path); } push_back (key_to_int ("")); } path += found_file; } else { path = inq_cmd_line (); push_back (key_to_int ("")); } } } else { beep (); path = inq_cmd_line (); push_back (key_to_int ("")); } set_buffer (old_buf); /* ** We assume that if someone wants to keep the file_list_buf ** around, they'll set it to 0 using put_parm. If it's non-zero, ** we assume it's OK to delete the buffer. */ if (file_list_buf) delete_buffer (file_list_buf); returns (path); } } /* ** If the bad key wasn't a Tab, we check to see if we have to ** to do history stuff. */ else if (!_history_buf) { push_back (key_pressed); returns (0); } else { int line, pass; string prompt; set_msg_level (3); set_buffer (_history_buf); inq_position (line); switch (key_pressed) { case key_to_int (""): prompt = _last_response; case key_to_int (""): case key_to_int (""): { if (line != 1) while (pass < 2 && (!strlen (prompt) || prompt == inq_cmd_line ())) { down (); if (inq_position () || index (read (), "]::[")) { pass++; search_back ("]::[", 0); down (); beginning_of_line (); } prompt = read (); prompt = substr (prompt, 1, strlen (prompt) - 1); } } case key_to_int (""): case key_to_int (""): { if (line != 1) while (pass < 2 && (!strlen (prompt) || prompt == inq_cmd_line ())) { if (index (read (), "]::[")) { down (); if (search_fwd ("]::[", 0)) up (); else end_of_buffer (); beginning_of_line (); } else up (); prompt = read (); if (index (prompt, "]::[")) { pass++; down (); if (search_fwd ("]::[", 0)) up (); else end_of_buffer (); beginning_of_line (); prompt = read (); } prompt = substr (prompt, 1, strlen (prompt) - 1); } } } set_buffer (old_buf); set_msg_level (old_msg_level); if (strlen (prompt)) returns (prompt); else { push_back (key_pressed); returns (0); } } } /* ** _complete_action: ** ** When the user presses at the file list menu, mark the ** selected file with a greater-than symbol and force an exit from ** the menu. The macro relies on the dialog manager's guarantee that ** the cursor position when a button is picked will be the beginning ** of the button line in the menu buffer in order to mark the button ** in the least amount of time. */ int _complete_action (int event_type) { switch (event_type) { case DIALOG_PICK_MENU: _dialog_esc (); case DIALOG_INIT: { /* ** We save the command assigned to by assigning ** it to a key that's not used within menus. This ** uses less memory than a global string. */ assign_to_key ("#255", inq_assignment ("")); assign_to_key ("", "_complete_dir"); /* mouse_prompt will super class the previous menu handler */ pre_func = inq_mouse_action(); set_mouse_action( "mouse_prompt" ); // mouse event function } case DIALOG_TERM: { assign_to_key ("", inq_assignment ("#255")); assign_to_key ("#255", "self_insert"); } } returns (TRUE); } /* ** _complete_dir: ** ** If is pressed while a directory is highlighted in the file ** list, we immediately expand the list of files in that directory by ** pretending was pressed. */ void _complete_dir () { extern _dialog_menu_pick; if (search_string ("[/\\\\]", substr (read (), 2), NULL, TRUE)) _dialog_menu_pick (); else beep (); } /* ** _complete_select: ** ** This function allows the user to select a group of files from the ** list. For each one selected, we toggle a selection character (currently ** '>') on and off. Note that you can't select directories with , ** and the selected files are ignored if you have a group of selected files ** and press or on a directory. */ void _complete_select () { if (search_string ("[/\\\\]", read (), NULL, TRUE)) beep (); else if (read (1) == ">") delete_char (); else { insert (">"); prev_char (); } } /* ** _complete_select_all: ** ** This function toggles selection of all the files in the list. */ void _complete_select_all () { save_position (); top_of_buffer (); while (!inq_position ()) { if (!search_string ("[/\\\\]", read (), NULL, TRUE)) _complete_select (); down (); } if (read (1) != ">") { top_of_buffer (); if (search_fwd ("<\\>")) { raise_anchor (); drop_anchor (3); restore_position (FALSE); } else restore_position (); } else restore_position (); } /* ** _prompt_end: ** ** After the prompting operation has been completed, this macro inserts the ** response into the buffer if the prompt hasn't been cancelled and it's more ** than one character long (we don't want to bother retaining [ynw] answers). */ int _prompt_end () { if (_history_buf) { int old_buf, old_msg_level; string response, prompt; old_msg_level = inq_msg_level (); set_msg_level (3); old_buf = inq_buffer (); set_buffer (_history_buf); top_of_buffer (); response = inq_cmd_line (); prompt = read (); prompt = substr (prompt, 1, strlen (prompt) - 1); delete_to_eol (); if (strlen (response) > 1) { _last_response = response; if ("Command cancelled." != inq_message ()) { int start_line, end_line; if (!search_fwd ("[" + inq_command () + "]::[" + prompt + "]", 0)) { end_of_buffer (); down (); beginning_of_line (); insert ("[" + inq_command () + "]::[" + prompt + "]"); } down (); inq_position (start_line); beginning_of_line (); insert (response + (inq_position () ? "" : "\n")); move_abs (start_line, 1); response = read (); if (search_fwd ("]::[", 0)) up (); else end_of_buffer (); inq_position (end_line); if (end_line - start_line >= 10) delete_line (); move_abs (start_line, 1); down (); while (!inq_position () && !index (read (), "]::[")) { if (response == read ()) { delete_line (); break; } down (); } } } restore_position (); set_msg_level (old_msg_level); set_buffer (old_buf); } returns (0); } /* ** mouse_prompt ** ** super-class the default menu handler when a list of files is displayed ** ** This super classing makes the menu act more like a list box ** ** Mouse action assignments: ** default menu actions plus ** Dbl Click 1 - select and edit the file(s) ** Click 1 - select / unselect a file for editing same as space ** Click 1 - toggle all selections same as * ** */ void mouse_prompt (int event, // the event code int modifier, // modifier keys int parm2, // line or scroll bar code int parm3 ) // column or thumb position { /* handle the events of interest */ switch ( event ){ case BTN1_DBLCLK: // edit this file and all _dialog_menu_pick(); // previously selected files case BTN1_CLICK: if ( modifier == KB_SHIFT ) { _complete_select_all(); // toggle all selections } else if ( modifier == 0 ) { _complete_select(); // select / unselect this file } else { execute_macro( pre_func, event, modifier, parm2, parm3 ); } case BTN2_CLICK: // exit the prompt window exit(); default: /* let the previous event handler for menus process the event */ execute_macro( pre_func, event, modifier, parm2, parm3 ); } }