ftp.nice.ch/pub/next/unix/editor/ne-1.0.NI.s.tar.gz#/ne-1.0.NI.s/src/menu.c

This is menu.c in view mode; [Download] [Up]

/* Menu handling function. Includes also key and menu configuration parsing.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */


#include "ne.h"
#include "termchar.h"

/* The default number of menus. */

#define DEF_MENU_NUM 8

/* The number of extras spaces around each menu item, with and without standout. */

#define MENU_EXTRA 2
#define MENU_NOSTANDOUT_EXTRA 4

/* The maximum length of the status bar, excluding the file name. */

#define MAX_BAR_BUFFER_SIZE 128

/* The maximum length of the flag string. */

#define MAX_FLAG_STRING_SIZE 32

/* The name of the menu configuration file. */

#define MENU_CONF_NAME ".menus"

/* The name of the key bindings file. */

#define KEY_BINDINGS_NAME ".keys"


/* The keywords used in the configuration files. */

#define MENU_KEYWORD "MENU"
#define ITEM_KEYWORD "ITEM"
#define KEY_KEYWORD "KEY"


/* This structure defines a menu item. command_line points to
the command line to be executed when the menu item is selected. */

typedef struct {
	const char *text;
	const char *command_line;
} menu_item;



/* This structure defines a menu. It contains number of items, the
horizontal position of the menu, its width, the current item, the menu
name and a pointer to the item array. Note that xpos has to be greater
than zero. */

typedef struct {
	int item_num;
	int xpos, width;
	int cur_item;
	const char *text;
	const menu_item *items;
} menu;



/* The following structures describe ne's standard menus. */

static menu_item project_item[] =
	{
		{ "Open...  ^O", OPEN_ABBREV },
		{ "Open New...", OPENNEW_ABBREV },
		{ "Save     ^S", SAVE_ABBREV },
		{ "Save As... ", SAVEAS_ABBREV },
		{ "Clear      ", CLEAR_ABBREV },
		{ "Quit       ", QUIT_ABBREV },
		{ "Exit       ", EXIT_ABBREV },
		{ "About      ", ABOUT_ABBREV }
	};

static const menu_item documents_item[] =
	{
		{ "New       ^D", NEWDOC_ABBREV },
		{ "Close     ^Q", CLOSEDOC_ABBREV },
		{ "Next      f2", NEXTDOC_ABBREV },
		{ "Prev      f3", PREVDOC_ABBREV },
		{ "Select... f4", SELECTDOC_ABBREV }
	};

static const menu_item edit_item[] =
	{
		{ "Mark Block   ^B", MARK_ABBREV },
		{ "Cut          ^X", CUT_ABBREV },
		{ "Copy         ^C", COPY_ABBREV },
		{ "Paste        ^V", PASTE_ABBREV },
		{ "Erase        ^E", ERASE_ABBREV },
		{ "Through        ", THROUGH_ABBREV },
		{ "Delete Line  ^Y", DELETELINE_ABBREV },
		{ "Mark Vert    ^@", MARKVERT_ABBREV },
		{ "Paste Vert   ^W", PASTEVERT_ABBREV },
		{ "Open Clip      ", OPENCLIP_ABBREV },
		{ "Save Clip      ", SAVECLIP_ABBREV }
	};


static const menu_item search_item[] =
	{
		{ "Find...        ^F", FIND_ABBREV },
		{ "Find RegExp... ^_", FINDREGEXP_ABBREV },
		{ "Replace...     ^R", REPLACE_ABBREV },
		{ "Replace Once...  ", REPLACEONCE_ABBREV },
		{ "Replace All...   ", REPLACEALL_ABBREV },
		{ "Repeat Last    ^G", REPEATLAST_ABBREV },
		{ "Goto Line...   ^J", GOTOLINE_ABBREV },
		{ "Goto Col...      ", GOTOCOLUMN_ABBREV },
		{ "Goto Mark        ", GOTOMARK_ABBREV },
		{ "Match Bracket  ^]", MATCHBRACKET_ABBREV }
	};


static const menu_item macros_item[] =
	{
		{ "Start/Stop Rec ^T", RECORD_ABBREV },
		{ "Play Once      f9", PLAYONCE_ABBREV },
		{ "Play Many...     ", PLAY_ABBREV },
		{ "Play Macro...    ", MACRO_ABBREV },
		{ "Open Macro...    ", OPENMACRO_ABBREV },
		{ "Save Macro...    ", SAVEMACRO_ABBREV },
	};


static const menu_item extras_item[] =
	{
		{ "Exec...    ^K", EXEC_ABBREV },
		{ "Help...   f10", HELP_ABBREV },
		{ "Refresh    ^L", REFRESH_ABBREV },
		{ "Undo       f5", UNDO_ABBREV },
		{ "Redo       f6", REDO_ABBREV },
		{ "Undel Line ^U", UNDELLINE_ABBREV },
		{ "Center       ", CENTER_ABBREV },
		{ "Paragraph    ",PARAGRAPH_ABBREV },
		{ "ToUpper      ", TOUPPER_ABBREV },
		{ "ToLower      ", TOLOWER_ABBREV },
		{ "Capitalize   ", CAPITALIZE_ABBREV }
	};



static const menu_item navigation_item[] =
	{
		{ "Move Left      ", MOVELEFT_ABBREV },
		{ "Move Right     ", MOVERIGHT_ABBREV },
		{ "Line Up        ", LINEUP_ABBREV },
		{ "Line Down      ", LINEDOWN_ABBREV },
		{ "Prev Page    ^P", PREVPAGE_ABBREV },
		{ "Next Page    ^N", NEXTPAGE_ABBREV },
		{ "Top/Bottom   ^^", TOGGLESEOF_ABBREV },
		{ "Beg Of Line  ^A", MOVESOL_ABBREV },
		{ "End Of Line  ^Z", MOVEEOL_ABBREV },
		{ "Prev Word    f7", PREVWORD_ABBREV },
		{ "Next Word    f8", NEXTWORD_ABBREV }
	};



static const menu_item prefs_item[] =
	{
		{ "Tab Size...     ", TABSIZE_ABBREV },
		{ "Insert/Over  Ins", INSERT_ABBREV },
		{ "Free Form       ", FREEFORM_ABBREV },
		{ "Status Bar      ", STATUSBAR_ABBREV },
		{ "Fast GUI        ", FASTGUI_ABBREV },
		{ "Word Wrap       ", WORDWRAP_ABBREV },
		{ "Right Margin    ", RIGHTMARGIN_ABBREV },
		{ "Auto Indent     ", AUTOINDENT_ABBREV },
		{ "Load Prefs...   ", LOADPREFS_ABBREV },
		{ "Save Prefs...   ", SAVEPREFS_ABBREV },
		{ "Load Auto Prefs ", LOADAUTOPREFS_ABBREV },
		{ "Save Auto Prefs ", SAVEAUTOPREFS_ABBREV },
		{ "Save Def Prefs  ", SAVEDEFPREFS_ABBREV },
	};

static menu def_menus[DEF_MENU_NUM] = {
	{
		sizeof(project_item)/sizeof(menu_item),
		1,
		11,
		0,
		"Project",
		project_item
	},
	{
		sizeof(documents_item)/sizeof(menu_item),
		9,
		12,
		0,
		"Documents",
		documents_item
	},
	{
		sizeof(edit_item)/sizeof(menu_item),
		19,
		15,
		0,
		"Edit",
		edit_item
	},
	{
		sizeof(search_item)/sizeof(menu_item),
		24,
		17,
		0,
		"Search",
		search_item
	},
	{
		sizeof(macros_item)/sizeof(menu_item),
		31,
		17,
		0,
		"Macros",
		macros_item
	},
	{
		sizeof(extras_item)/sizeof(menu_item),
		38,
		13,
		0,
		"Extras",
		extras_item
	},
	{
		sizeof(navigation_item)/sizeof(menu_item),
		45,
		15,
		0,
		"Navigation",
		navigation_item
	},
	{
		sizeof(prefs_item)/sizeof(menu_item),
		56,
		16,
		0,
		"Prefs",
		prefs_item
	}
};



/* current_menu remembers the last menu activated. menu_num is the number of
menus. */

static int current_menu = 0, menu_num = DEF_MENU_NUM;


/* menus points to an array of menu_num menu structures. */

static menu *menus = def_menus;


static void draw_cur_item(int n) {
	move_cursor(1+menus[n].cur_item, menus[n].xpos);
	if (!cur_buffer->fast_gui && standout_ok) output_chars(menus[n].items[menus[n].cur_item].text, menus[n].width-(cursor_on_off_ok ? 0 : 1));
}


static void undraw_cur_item(int n) {
	if (!cur_buffer->fast_gui && standout_ok)  {
		standout_on();
		move_cursor(1+menus[n].cur_item, menus[n].xpos);
		output_chars(menus[n].items[menus[n].cur_item].text, menus[n].width-(cursor_on_off_ok ? 0 : 1));
		standout_off();
	}
}


/* This function draws a given menu. It also draws the current menu item. */

static void draw_menu(int n) {
	int i;

	assert(menus[n].xpos > 0);

	move_cursor(0, menus[n].xpos);
	output_string(menus[n].text);

	for(i=0; i<menus[n].item_num; i++) {
		move_cursor(i+1, menus[n].xpos-1);

		if (!standout_ok) output_chars("|", 1);

		standout_on();
		output_chars(NULL, 1);
		output_string(menus[n].items[i].text);
		output_chars(NULL, 1);
		standout_off();

		if (!standout_ok) output_chars("|", 1);
	}

	if (!standout_ok) {
		move_cursor(i+1, menus[n].xpos-1);
		for(i=0; i<menus[n].width+(standout_ok ? MENU_EXTRA : MENU_NOSTANDOUT_EXTRA); i++) output_chars("-", 1);
	}

	draw_cur_item(n);
}


/* This function undraws a menu. This is obtained by refreshing part of the
screen via mvaddstrn(). */

static void undraw_menu(int n) {
	int i;
	line_desc *ld = cur_buffer->top_line_desc;

	standout_on();
	move_cursor(0, menus[n].xpos);
	output_string(menus[n].text);
	standout_off();

	for(i=1; i<=menus[n].item_num+(standout_ok ? 0 : 1); i++) {
		if (ld->ld_node.next->next) {
			ld = (line_desc *)ld->ld_node.next;
			mvaddstrn(i, menus[n].xpos-1, ld, cur_buffer->win_x+menus[n].xpos-1, menus[n].width+(standout_ok ? MENU_EXTRA : MENU_NOSTANDOUT_EXTRA), cur_buffer->tab_size, FALSE);
		}
		else {
			move_cursor(i, menus[n].xpos-1);
			clear_to_eol();
		}
	}
}



static void draw_next_item(void) {
	undraw_cur_item(current_menu);
	menus[current_menu].cur_item = (menus[current_menu].cur_item+1) % menus[current_menu].item_num;
	draw_cur_item(current_menu);
}

static void draw_prev_item(void) {
	undraw_cur_item(current_menu);
	if (--(menus[current_menu].cur_item) < 0) menus[current_menu].cur_item = menus[current_menu].item_num-1;
	draw_cur_item(current_menu);
}

static void draw_item(int item) {
	undraw_cur_item(current_menu);
	menus[current_menu].cur_item = item;
	draw_cur_item(current_menu);
}

static void draw_next_menu(void) {
	undraw_menu(current_menu);
	current_menu = ++current_menu % menu_num;
	draw_menu(current_menu);
}

static void draw_prev_menu(void) {
	undraw_menu(current_menu);
	if (--current_menu < 0) current_menu = menu_num-1;
	draw_menu(current_menu);
}

int search_menu_title(int n, int c) {

	int i;

	for(i=0; i<menu_num-1; i++)
		if (menus[++n % menu_num].text[0] == c) return(n % menu_num);

	return(-1);
}

int search_menu_item(int n, int c) {

	int i,j;

	c = toupper(c);

	for(i=0, j=menus[n].cur_item; i<menus[n].item_num-1; i++)
		if (menus[n].items[++j % menus[n].item_num].text[0] == c) return(j % menus[n].item_num);

	return(-1);
}



static void item_search(int c) {
	int new_item;

	if (c >= 'a' && c <= 'z') {
		new_item = search_menu_item(current_menu, c);
		if (new_item >= 0) draw_item(new_item);
	}
	else if (c >= 'A' && c <= 'Z') {
		new_item = search_menu_title(current_menu, c);
		if (new_item >= 0) {
			undraw_menu(current_menu);
			current_menu = new_item;
			draw_menu(current_menu);
		}
	}
}


static void draw_first_menu(void) {

	int i = 0, n = 0;

	move_cursor(0,0);

	standout_on();
	if (standout_ok) cursor_off();

	while(i<columns) {
		output_chars(NULL, 1);
		i++;
		if (n < menu_num) {
			output_string(menus[n].text);
			i += strlen(menus[n].text);
			n++;
		}
	}

	if (standout_ok) standout_off();

	draw_menu(current_menu);
}

static void undraw_last_menu(void) {
	undraw_menu(current_menu);
	update_line(cur_buffer, 0);
	cursor_on();
}


static void do_menu_action(void) {
	undraw_last_menu();
	execute_command_line(cur_buffer, menus[current_menu].items[menus[current_menu].cur_item].command_line);
}



/* showing_msg tells draw_status_bar() that a message is currently shown, and should be
cancelled only on the next refresh. Bar gone says that the status bar doesn't exists any
longer, so we have to rebuild it entirely. */

static int showing_msg;
static int bar_gone = TRUE;



/* This function resets the status bar. It does not perform the refresh, just
sets bar_gone to TRUE. */

void reset_status_bar(void) {
	bar_gone = TRUE;
}



/* This support function returns a copy of the status string which is
never longer than MAX_FLAG_STRING_SIZE characters. The string is kept in
a static buffer which is overwritten at each call. Note that the string
includes a leading space. This way, if both the line numbers and
the flags are updated the cursor does not need to be moved after
printing the numbers (an operation which usually needs the output
of several characters). */

char *gen_flag_string(buffer *b) {

	static char string[MAX_FLAG_STRING_SIZE];
	int i = 0;

	string[i++] = ' ';
	string[i++] = b->insert ? 'i' : ' ';
	string[i++] = b->auto_indent ? 'a' : ' ';
	string[i++] = b->search_back ? 'b' : ' ';
	string[i++] = b->case_search ? 'c' : ' ';
	string[i++] = b->word_wrap ? 'w' : ' ';
	string[i++] = b->free_form ? 'f' : ' ';
	string[i++] = b->auto_prefs ? 'p' : ' ';
	string[i++] = b->verbose_macros ? 'v' : ' ';
	string[i++] = b->do_undo ? 'u' : ' ';
	string[i++] = b->read_only ? 'r' : ' ';
	string[i++] = b->turbo ? 't' : ' ';
	string[i++] = b->binary ? 'B' : ' ';
	string[i++] = b->marking ? (b->mark_is_vertical ? 'V' :'M') : ' ';
	string[i++] = b->recording ? 'R' : ' ';
	string[i++] = b->buffer_is_modified ? '*' : ' ';
	string[i] = 0;

	assert(i<MAX_FLAG_STRING_SIZE);

	return(string);
}



/* This function draws the status bar. If showing_msg is TRUE, it is set to
FALSE, bar_gone is set to TRUE and the update is deferred to the next
call. If the bar is not completely gone, we try to just update the line
and column numbers, and the flags. The function keeps track internally of
their last values, so that unnecessary printing is avoided. */


void draw_status_bar(void) {

	static char bar_buffer[MAX_BAR_BUFFER_SIZE];
	static char flag_string[MAX_FLAG_STRING_SIZE];
	static int x = -1, y = -1;

	char *p;
	int cur_pos, len;
	int i;

	if (showing_msg) {
		showing_msg = FALSE;
		bar_gone = TRUE;
		return;
	}

	if (!bar_gone && cur_buffer->status_bar) {
		if (!cur_buffer->fast_gui && standout_ok) standout_on();

		if (y != cur_buffer->cur_line || x != cur_buffer->win_x+cur_buffer->cur_x) {
			x = cur_buffer->win_x+cur_buffer->cur_x;
			y = cur_buffer->cur_line;
			i = sprintf(bar_buffer, "%6ld C:%6ld", y+1, x+1);
			move_cursor(lines-1, cur_buffer->fast_gui || !standout_ok ? 5: 3);
			output_chars(bar_buffer, i);
		}

		if (strcmp(flag_string, p = gen_flag_string(cur_buffer))) {
			strcpy(flag_string, p);
			move_cursor(lines-1, cur_buffer->fast_gui || !standout_ok ? 20: 18);
			output_string(flag_string);
		}

		if (!cur_buffer->fast_gui && standout_ok) standout_off();
		return;
	}


	if (cur_buffer->status_bar) {
		move_cursor(lines-1, 0);
		if (!cur_buffer->fast_gui && standout_ok) standout_on();

		strcpy(flag_string, gen_flag_string(cur_buffer));

		x = cur_buffer->win_x+cur_buffer->cur_x;
		y = cur_buffer->cur_line;

		len = sprintf(bar_buffer, cur_buffer->fast_gui || !standout_ok ? ">> L:%6ld C:%6ld%s " : " L:%6ld C:%6ld%s ", y+1, x+1, flag_string);
		if (len > columns) len = columns;

		move_cursor(lines-1, 0);

		output_chars(bar_buffer, len);
		cur_pos = len;

		if (cur_buffer->filename && cur_pos < columns) {
			len = strlen(cur_buffer->filename);
			if (cur_pos+len >= columns) len = columns-cur_pos;
			output_chars(cur_buffer->filename, len);
			cur_pos += len;
		}

		if (!cur_buffer->fast_gui && standout_ok) {
			output_chars(NULL, columns-cur_pos);
			standout_off();
		}
		else clear_to_eol();
	}
	else if (bar_gone) {
		move_cursor(lines-1, 0);
		clear_to_eol();
	}

	bar_gone = FALSE;
}



/* This function prints a message over the status bar. It also sets
showing_msg and bar_gone. */

void print_message(const char *message) {

	move_cursor(lines-1, 0);

	if (cur_buffer->fast_gui || !standout_ok || !cur_buffer->status_bar) {
		clear_to_eol();
		output_string(message);
	}
	else {
		standout_on();
		output_string(message);
		output_chars(NULL, columns-strlen(message));
		standout_off();
	}

	fflush(stdout);

	showing_msg = TRUE;
}



/* This function prints an error on the status bar. error_num is a global
error code. The function returns the error code passed, and does not do
anything if the error code is OK or ERROR. */

int print_error(int error_num) {

	assert(error_num < ERROR_COUNT);

	if (error_num > 0) {
		print_message(error_msg[error_num]);
		ring_bell();
	}
	return(error_num);
}



/* This function prints an information on the status bar. info_num is a global
information code. Note that no beep is generated. */


void print_info(int info_num) {

	assert(info_num < INFO_COUNT);

	print_message(info_msg[info_num]);
}



/* This function handles the menu system: it displays the menus, parses the
keyboard input, and eventually executes the correct command line. Note that
we support ':' for going to the command line, alphabetic search (upper case
for menus, lower case for items) and the cursor movement keys (by line,
character, page). Note also the all other actions are executed, so that you
can use shortcuts while using menus. */

void handle_menus(void) {

	input_class ic;
	action a;
	int c, n;
	char  *p;

	draw_first_menu();

	while(TRUE) {
		while((ic = char_class[(c = get_key_code()) & 0x1FF]) == IGNORE);

		switch(ic) {
			case ALPHA:
				if (c == ':') {
					undraw_last_menu();
					do_action(cur_buffer, EXEC, -1, NULL);
					return;
				}
				item_search(c);
				break;

			case RETURN:
				do_menu_action();
				return;

			case COMMAND:
				if ((a = parse_command_line(key_binding[c], &n, &p, FALSE))>=0) {
					switch(a) {
						case MOVE_LEFT:
							draw_prev_menu();
							break;

						case MOVE_RIGHT:
							draw_next_menu();
							break;

						case LINE_UP:
							draw_prev_item();
							break;

						case LINE_DOWN:
							draw_next_item();
							break;

						case PREV_PAGE:
							draw_item(0);
							break;

						case NEXT_PAGE:
							draw_item(menus[current_menu].item_num-1);
							break;

						case ESCAPE:
							undraw_last_menu();
							return;

						default:
							undraw_last_menu();
							do_action(cur_buffer, a, n, p);
							return;
					}
				}
				break;

			default:
				break;
		}

	}
}

static void error_in_menu_configuration(int line, char  *s) {

	fprintf(stderr, "Error in menu configuration file at line %d: %s\n", line, s);
	exit(0);
}


void get_menu_configuration(void) {

	char  *prefs_dir, *menu_conf;
	char_stream *cs;
	char  *p;
	int pass, cur_menu, cur_item, num_items_in_menu, line;
	menu *new_menus;
	menu_item *new_items;

	if (prefs_dir = exists_prefs_dir()) {
		if (menu_conf = malloc(strlen(prefs_dir)+strlen(MENU_CONF_NAME)+1)) {
				strcat(strcpy(menu_conf, prefs_dir), MENU_CONF_NAME);

			if (cs = load_stream(NULL, menu_conf)) {

				for(pass=0; pass<2; pass++) {

					p = cs->stream;
					line = 1;
					cur_menu = -1;
					cur_item = num_items_in_menu = 0;

					while(p-cs->stream<cs->len) {
						if (*p) {

							if (!cmdcmp(MENU_KEYWORD, p)) {

								if (cur_menu < 0 || num_items_in_menu) {
									cur_menu++;
									num_items_in_menu = 0;

									if (pass) {
										while(*p && *p++ != '"');
										if (*p) {
											new_menus[cur_menu].text = p;
											while(*p && *++p != '"');
											if (*p) {
												*p++ = 0;
												if (cur_menu == 0) new_menus[0].xpos = 1;
												else new_menus[cur_menu].xpos = new_menus[cur_menu-1].xpos+strlen(new_menus[cur_menu-1].text)+1;
												new_menus[cur_menu].items = &new_items[cur_item];
											}
											else error_in_menu_configuration(line, "menu name has to end with quotes.");
										}
										else error_in_menu_configuration(line, "menu name has to start with quotes.");
									}
								}
								else if (cur_menu >= 0) error_in_menu_configuration(line-1, "no items specified for this menu.");

							}
							else if (!cmdcmp(ITEM_KEYWORD, p)) {
								if (cur_menu < 0) error_in_menu_configuration(line, "no menu specified for this item.");

								if (pass) {
									while(*p && *p++ != '"');
									if (*p) {
										new_items[cur_item].text = p;
										while(*p && *++p != '"');
										if (*p) {
											*p++ = 0;
											if (num_items_in_menu == 0 || strlen(new_items[cur_item].text) == new_menus[cur_menu].width) {
												if (num_items_in_menu == 0) {
													if ((new_menus[cur_menu].width = strlen(new_items[cur_item].text)) == 0)
														error_in_menu_configuration(line, "menu item name width has to be greater than zero.");
												}
												while (isspace(*p)) p++;
												if (*p) {
													new_items[cur_item].command_line = p;
													new_menus[cur_menu].item_num = num_items_in_menu+1;
												}
												else error_in_menu_configuration(line, "no command specified.");
											}
											else error_in_menu_configuration(line, "menu item name width has to be constant throughout the menu.");
										}
										else error_in_menu_configuration(line, "menu item name has to end with quotes.");
									}
									else error_in_menu_configuration(line, "menu item name has to start with quotes.");
								}
								num_items_in_menu++;
								cur_item++;
							}

						}
						line++;
						p += strlen(p)+1;
					}

					if (pass == 0) {
						if (!num_items_in_menu) error_in_menu_configuration(line-1, "no items specified for this menu.");
						if (cur_menu == -1 || cur_item == 0) error_in_menu_configuration(line, "no menus or items specified.");

						if (!(new_menus = calloc(cur_menu+1, sizeof(menu))) || !(new_items = calloc(cur_item, sizeof(menu_item))))
							error_in_menu_configuration(line, "not enough memory.");
					}
					else {
						menu_num = cur_menu+1;
						menus = new_menus;
					}
				}
			}

			free(menu_conf);
		}
	}
}


static void error_in_key_bindings(int line, char *s) {

	fprintf(stderr, "Error in key bindings file at line %d: %s\n", line, s);
	exit(0);
}

void get_key_bindings(void) {
	char *prefs_dir, *key_bindings;
	char_stream *cs;
	char *p;
	int c, line;

	if (prefs_dir = exists_prefs_dir()) {
		if (key_bindings = malloc(strlen(prefs_dir)+strlen(KEY_BINDINGS_NAME)+1)) {
			strcat(strcpy(key_bindings, prefs_dir), KEY_BINDINGS_NAME);

			if (cs = load_stream(NULL, key_bindings)) {

				p = cs->stream;
				line = 1;

				while(p-cs->stream<cs->len) {
					if (*p && !cmdcmp(KEY_KEYWORD, p)) {
						while(*p && !isspace(*p)) p++;

						if (sscanf(p, "%x %*s", &c) == 1) {
							if (c>=0 && c<NUM_KEYS) {
								if (c != 27 && c != 13) {
									while(isspace(*p)) p++;
									while(*p && !isspace(*p)) p++;
									while(isspace(*p)) p++;
									if (*p) key_binding[c] = p;
									else error_in_key_bindings(line, "no command specified.");
								}
								else error_in_key_bindings(line, "you cannot redefine ESCAPE and RETURN.");
							}
							else error_in_key_bindings(line, "key code out of range.");
						}
						else error_in_key_bindings(line, "can't read key code.");
					}

					line++;
					p += strlen(p)+1;
				}
			}

			free(key_bindings);
		}
	}
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.