Files
CydandClaude Fable 5 db7745fcd0 sda4: commit the Glaze developer hard-drive dump
Un-ignored: the dev drive is the ground truth the restoration and
emulator work constantly reference (DPL3/LIBDPL + VRENDER i860 renderer
source, BT/RP live+dev game trees, VGL_LABS pod boot, scene/audio
content). Kept in-repo for the pod-owner community.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:41:15 -05:00

835 lines
20 KiB
Plaintext

/*
** 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 <Tab>, 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 ("<Tab>"))
{
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 <End>
** to prevent the command line being highlighted, sometimes
** an <Enter> to force the command to take the argument.
**
** If the end result is a directory specifier, we push back
** a <Tab> to force expansion on the directory.
*/
if (num_files)
{
if (num_files == 1)
{
top_of_buffer ();
path += trim (read ());
push_back (key_to_int ("<End>"));
}
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 ("<Space>", "_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 ("<End>"));
push_back (-1);
push_back (key_to_int ("<Tab>"));
}
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 ("<Enter>"));
}
path += found_file;
}
else
{
path = inq_cmd_line ();
push_back (key_to_int ("<End>"));
}
}
}
else
{
beep ();
path = inq_cmd_line ();
push_back (key_to_int ("<End>"));
}
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 ("<Alt-l>"):
prompt = _last_response;
case key_to_int ("<Up>"):
case key_to_int ("<Keypad-Up>"):
{
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 ("<Down>"):
case key_to_int ("<Keypad-Down>"):
{
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 <Enter> 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 <Tab> 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 ("<Tab>"));
assign_to_key ("<Tab>", "_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 ("<Tab>", inq_assignment ("#255"));
assign_to_key ("#255", "self_insert");
}
}
returns (TRUE);
}
/*
** _complete_dir:
**
** If <Tab> is pressed while a directory is highlighted in the file
** list, we immediately expand the list of files in that directory by
** pretending <Enter> 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 <Space>,
** and the selected files are ignored if you have a group of selected files
** and press <Enter> or <Tab> 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
** <Shift> 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 );
}
}