/* * The "macro" routines in TDE are not really macro routines in the classical * sense. In TDE, "macros" are just recordings of key sequences. But, * actually, I find the ability to record and playback keystrokes generally * more useful than writing a macro. Being that I'm so stupid, it takes me * half the morning to write and half the afternoon to debug a simple classical * macro. For most of my routine, little jobs, I find it more convenient * to record my keystrokes and playback those keystrokes at appropriate places * in a file. It just seems easier for me to "teach" the editor what to do * rather than "instruct" the editor what to do. * * New editor name: TDE, the Thomson-Davis Editor. * Author: Frank Davis * Date: June 5, 1991, version 1.0 * Date: July 29, 1991, version 1.1 * Date: October 5, 1991, version 1.2 * Date: January 20, 1992, version 1.3 * Date: February 17, 1992, version 1.4 * Date: April 1, 1992, version 1.5 * Date: June 5, 1992, version 2.0 * Date: October 31, 1992, version 2.1 * Date: April 1, 1993, version 2.2 * Date: June 5, 1993, version 3.0 * Date: August 29, 1993, version 3.1 * Date: November 13, 1993, version 3.2 * Date: June 5, 1994, version 4.0 * * This modification of Douglas Thomson's code is released into the * public domain, Frank Davis. You may distribute it freely. */ #include "tdestr.h" /* tde types */ #include "common.h" #include "define.h" #include "tdefunc.h" /* * keystroke record functions */ /* * Name: record_on_off * Purpose: save keystrokes in keystroke buffer * Date: April 1, 1992 * Passed: window: pointer to current window * Notes: -1 in .next field indicates the end of a recording * -1 in .key field indicates the initial, unassigned macro key * STROKE_LIMIT+1 in .next field indicates an unused space. */ int record_on_off( TDE_WIN *window ) { register int next; int prev; int line; int key; int func; char temp[MAX_COLS+2]; #if defined( __UNIX__ ) chtype display_buff[MAX_COLS+2]; /* chtype is defined in curses.h */ #else char display_buff[(MAX_COLS+2)*2]; #endif mode.record = !mode.record; if (mode.record == TRUE) { line = window->bottom_line; show_avail_strokes( ); save_screen_line( 0, line, display_buff ); /* * press key that will play back recording */ set_prompt( main11, line ); /* * get the candidate macro key and look up the function assigned to it. */ key = getkey( ); func = getfunc( key ); /* * the key must be an unused, recognized function key or a function * key assigned to a previously defined macro. we also need room * in the macro structure. */ if (key <= 256 || (func != 0 && func != PlayBack)) { /* * cannot assign a recording to this key */ error( WARNING, line, main12 ); mode.record = FALSE; } else if (g_status.stroke_count == 0) { /* * no more room in recording buffer */ error( WARNING, line, main13 ); mode.record = FALSE; } else { /* * everything is everything so far, just check for a prev macro */ prev = OK; if (func == PlayBack) { /* * overwrite recording (y/n)? */ set_prompt( main14, line ); if (get_yn( ) == A_NO) { prev = ERROR; mode.record = FALSE; } } if (prev == OK) { g_status.recording_key = key; next = macro.first_stroke[key-256]; /* * if key has already been assigned to a macro, clear macro def. */ if (next != STROKE_LIMIT+1) { do { prev = next; next = macro.strokes[next].next; macro.strokes[prev].key = MAX_KEYS+1; macro.strokes[prev].next = STROKE_LIMIT+1; ++g_status.stroke_count; } while (next != -1); show_avail_strokes( ); } /* * find the first open space and initialize */ for (next=0; macro.strokes[next].next != STROKE_LIMIT+1;) next++; macro.first_stroke[key-256] = next; macro.strokes[next].key = -1; macro.strokes[next].next = -1; key_func.key[key-256] = PlayBack; /* * recording */ s_output( main15, g_display.mode_line, 22, g_display.mode_color | 0x80 ); } } restore_screen_line( 0, line, display_buff ); } /* * the flashing "Recording" and the stroke count write over the modes. * when we get thru defining a macro, redisplay the modes. */ if (mode.record == FALSE) { memset( temp, ' ', 36 ); temp[36] = '\0'; s_output( temp, g_display.mode_line, 22, g_display.mode_color ); show_tab_modes( ); show_indent_mode( ); show_sync_mode( ); show_search_case( ); show_wordwrap_mode( ); /* * let's look at the macro. if the first .key of the macro is * still -1, which is the initial unassigned key in a macro, reset * the macro so other keys may be assigned to this node. */ key = g_status.recording_key; if (key != 0) { next = macro.first_stroke[key-256]; if (macro.strokes[next].key == -1) { macro.strokes[next].key = MAX_KEYS+1; macro.strokes[next].next = STROKE_LIMIT+1; macro.first_stroke[key-256] = STROKE_LIMIT+1; if (getfunc( key ) == PlayBack) key_func.key[key-256] = 0; } } g_status.recording_key = 0; } return( OK ); } /* * Name: record_keys * Purpose: save keystrokes in keystroke buffer * Date: April 1, 1992 * Passed: line: line to display prompts * Notes: -1 in .next field indicates the end of a recording * STROKE_LIMIT+1 in .next field indicates an unused space. */ void record_keys( int line ) { register int next; register int prev; int key; int func; if (mode.record == TRUE) { if (g_status.stroke_count == 0) /* * no more room in recording buffer */ error( WARNING, line, main13 ); else { key = g_status.key_pressed; func = getfunc( key ); if (func != RecordMacro && func != SaveMacro && func != LoadMacro && func != ClearAllMacros) { /* * a -1 in the next field marks the end of the keystroke recording. */ next = macro.first_stroke[g_status.recording_key - 256]; if (macro.strokes[next].next != STROKE_LIMIT+1) { while (macro.strokes[next].next != -1) next = macro.strokes[next].next; } prev = next; /* * now find an open space to record the current key. */ if (macro.strokes[next].key != -1) { for (; next < STROKE_LIMIT && macro.strokes[next].next != STROKE_LIMIT+1;) next++; if (next == STROKE_LIMIT) { for (next=0; next < prev && macro.strokes[next].next != STROKE_LIMIT+1;) next++; } } if (next == prev && macro.strokes[prev].key != -1) /* * no more room in recording buffer */ error( WARNING, line, main13 ); else { /* * next == prev if we are recording the initial macro node. */ macro.strokes[prev].next = next; macro.strokes[next].next = -1; macro.strokes[next].key = key; g_status.stroke_count--; show_avail_strokes( ); } } } } } /* * Name: show_avail_strokes * Purpose: show available free key strokes in lite bar at bottom of screen * Date: April 1, 1992 */ void show_avail_strokes( void ) { char temp[MAX_COLS+2]; s_output( main18, g_display.mode_line, 33, g_display.mode_color ); my_ltoa( g_status.stroke_count, temp, 10 ); s_output( " ", g_display.mode_line, 51, g_display.mode_color ); s_output( temp, g_display.mode_line, 51, g_display.mode_color ); } #if defined( __UNIX__ ) /* * Name: save_strokes * Purpose: save strokes to a file * Date: November 13, 1993 * Passed: window: pointer to current window */ int save_strokes( TDE_WIN *window ) { FILE *fp; /* file to be written */ register int rc; int prompt_line; char answer[PATH_MAX+2]; #if defined( __UNIX__ ) chtype display_buff[MAX_COLS+2]; /* chtype is defined in curses.h */ mode_t fattr; #else char display_buff[(MAX_COLS+2)*2]; int fattr; #endif answer[0] = '\0'; prompt_line = window->bottom_line; save_screen_line( 0, prompt_line, display_buff ); /* * name for macro file */ if ((rc = get_name( main19, prompt_line, answer, g_display.message_color )) == OK && *answer != '\0') { /* * make sure it is OK to overwrite any existing file */ rc = get_fattr( answer, &fattr ); if (rc == OK) { /* * file exists. make sure this is a regular file, first */ if (S_ISREG( fattr )) { /* * overwrite existing file? */ set_prompt( main20, prompt_line ); if (get_yn( ) != A_YES || change_mode( answer, prompt_line ) == ERROR) rc = ERROR; } else rc = ERROR; } else /* * file does not exist. take a chance on a valid file name. */ rc = OK; if (rc != ERROR) { if ((fp = fopen( answer, "wb" )) != NULL) { fwrite( ¯o.first_stroke[0], sizeof(int), MAX_KEYS, fp ); fwrite( ¯o.strokes[0], sizeof(STROKES), STROKE_LIMIT, fp ); fclose( fp ); } } } restore_screen_line( 0, prompt_line, display_buff ); return( OK ); } #else /* * Name: save_strokes * Purpose: save strokes to a file * Date: April 1, 1992 * Passed: window: pointer to current window */ int save_strokes( TDE_WIN *window ) { FILE *fp; /* file to be written */ register int rc; int prompt_line; char answer[MAX_COLS+2]; #if defined( __UNIX__ ) chtype display_buff[MAX_COLS+2]; /* chtype is defined in curses.h */ mode_t fattr; #else char display_buff[(MAX_COLS+2)*2]; int fattr; #endif answer[0] = '\0'; prompt_line = window->bottom_line; save_screen_line( 0, prompt_line, display_buff ); /* * name for macro file */ if ((rc = get_name( main19, prompt_line, answer, g_display.message_color )) == OK && *answer != '\0') { /* * make sure it is OK to overwrite any existing file */ rc = get_fattr( answer, &fattr ); if (rc == OK) { /* * overwrite existing file */ set_prompt( main20, prompt_line ); if (get_yn( ) != A_YES || change_mode( answer, prompt_line ) == ERROR) rc = ERROR; } if (rc != ERROR) { if ((fp = fopen( answer, "wb" )) != NULL) { fwrite( ¯o.first_stroke[0], sizeof(int), MAX_KEYS, fp ); fwrite( ¯o.strokes[0], sizeof(STROKES), STROKE_LIMIT, fp ); fclose( fp ); } } } restore_screen_line( 0, prompt_line, display_buff ); return( OK ); } #endif /* * Name: load_strokes * Purpose: load strokes from a file * Date: April 1, 1992 * Passed: window: pointer to current window * Notes: show the user a file pick list. I can never remember macro * file names or the directory in which they hide. might as well * give the user a file pick list. */ int load_strokes( TDE_WIN *window ) { register FILE *fp; /* file to be read */ char dname[MAX_COLS]; /* directory search pattern */ char stem[MAX_COLS]; /* directory stem */ register int rc; dname[0] = '\0'; /* * search path for macro file */ if (get_name( main21, window->bottom_line, dname, g_display.message_color ) == OK && *dname != '\0') { if (validate_path( dname, stem ) == OK) { rc = list_and_pick( dname, stem, window ); /* * if everything is everything, load in the file selected by user. */ if (rc == OK) { if ((fp = fopen( dname, "rb" )) != NULL && ceh.flag != ERROR) { fread( ¯o.first_stroke[0], sizeof(int), MAX_KEYS, fp ); fread( ¯o.strokes[0], sizeof(STROKES), STROKE_LIMIT, fp ); fclose( fp ); } if (ceh.flag == OK) connect_macros( ); } } else /* * invalid path or file name */ error( WARNING, window->bottom_line, main22 ); } return( OK ); } /* * Name: clear_macro * Purpose: reset all macro buffers, pointers, functions. * Date: April 1, 1992 * Notes: reset the available macro stroke count. reset all fields in * macro structure. clear any keys assigned to macros in the * function assignment array. */ int clear_macros( TDE_WIN *arg_filler ) { register int i; g_status.stroke_count = STROKE_LIMIT; for (i=0; ibottom_line, ed16 ); rc = ERROR; } g_status.macro_next = macro.first_stroke[g_status.key_pressed-256]; g_status.current_macro = g_status.key_pressed; key = macro.strokes[g_status.macro_next].key; /* * execute called macro at beginning of this do loop. */ continue; } else /* * recursive macro - break out of this inner loop * or else we may overflow the stack(s). */ break; } /* * just as we assert before the main editor routine, let's * assert in the macro function to make sure everything * is everything. */ #if defined( __MSC__ ) assert( window != NULL ); assert( window->file_info != NULL ); assert( window->file_info->line_list != NULL ); assert( window->file_info->line_list_end != NULL ); assert( window->file_info->line_list_end->len == EOF ); assert( window->visible == TRUE ); assert( window->rline >= 0 ); assert( window->rline <= window->file_info->length + 1 ); assert( window->rcol >= 0 ); assert( window->rcol < MAX_LINE_LENGTH ); assert( window->ccol >= window->start_col ); assert( window->ccol <= window->end_col ); assert( window->bcol >= 0 ); assert( window->bcol < MAX_LINE_LENGTH ); assert( window->bcol == window->rcol-(window->ccol - window->start_col) ); assert( window->start_col >= 0 ); assert( window->start_col < window->end_col ); assert( window->end_col < g_display.ncols ); assert( window->cline >= window->top_line ); assert( window->cline <= window->bottom_line ); assert( window->top_line > 0 ); assert( window->top_line <= window->bottom_line ); assert( window->bottom_line <= g_display.nlines ); assert( window->bin_offset >= 0 ); if (window->ll->next == NULL) assert( window->ll->len == EOF ); else assert( window->ll->len >= 0 ); assert( window->ll->len < MAX_LINE_LENGTH ); #endif #if defined( __UNIX__ ) refresh( ); #endif if (g_status.command >= 0 && g_status.command < NUM_FUNCS) rc = (*do_it[g_status.command])( window ); g_status.macro_next = macro.strokes[g_status.macro_next].next; } while (rc == OK && g_status.macro_next != -1); /* * if we have come the end of a macro definition and there * are no keys saved on the stack, we have finished our * macro. get out. */ if (g_status.macro_next == -1 && g_status.mstack_pointer < 0) rc = ERROR; else if (rc != ERROR && g_status.mstack_pointer >= 0) { /* * if this is a recursive macro, don't pop the stack * because we didn't push. * for a standard macro, get back the next key in the * calling macro. */ if (g_status.current_macro != g_status.key_pressed) { if (pop_macro_stack( &g_status.macro_next ) != OK) { error( WARNING, window->bottom_line, ed17 ); rc = ERROR; } else { popped = TRUE; key = macro.strokes[g_status.macro_next].key; } } } } } g_status.macro_executing = FALSE; } return( OK ); } /* * Name: push_macro_stack * Purpose: push the next key in a currently executing macro on local stack * Date: October 31, 1992 * Notes: finally got tired of letting macros only "jump" and not call * other macros. * the first time in, stack_pointer is -1. */ int push_macro_stack( int key ) { /* * first, make sure we have room to push the key. */ if (g_status.mstack_pointer+1 < MAX_KEYS) { /* * increment the stack pointer and store the pointer to the next key * of the currently executing macro. store the currently executing * macro, too. */ ++g_status.mstack_pointer; macro_stack[g_status.mstack_pointer].key = key; macro_stack[g_status.mstack_pointer].macro = g_status.current_macro; return( OK ); } else return( STACK_OVERFLOW ); } /* * Name: pop_macro_stack * Purpose: pop currently executing macro on local stack * Date: October 31, 1992 * Notes: finally got tired of letting macros only "jump" and not "call" * other macros. * pop the macro stack. stack pointer is pointing to last key saved * on stack. */ int pop_macro_stack( int *key ) { /* * before we pop the stack, make sure there is something in the stack. */ if (g_status.mstack_pointer >= 0) { /* * pop the pointer to the next key and the current macro, then * decrement the stack_pointer. */ *key = macro_stack[g_status.mstack_pointer].key; g_status.current_macro = macro_stack[g_status.mstack_pointer].macro; --g_status.mstack_pointer; return( OK ); } else return( STACK_UNDERFLOW ); } /* * Name: macro_pause * Purpose: Enter pause state for macros * Date: June 5, 1992 * Passed: arg_filler: argument to satify function prototype * Returns: ERROR if the ESC key was pressed, OK otherwise. * Notes: this little function is quite useful in macro definitions. if * it is called near the beginning of a macro, the user may decide * whether or not to stop the macro. */ int macro_pause( TDE_WIN *arg_filler ) { int c; /* * tell user we are paused. the '| 0x80' turns on the blink attribute */ s_output( paused1, g_display.mode_line, 23, g_display.mode_color | 0x80 ); s_output( paused2, g_display.mode_line, 23+strlen( paused1 ), g_display.mode_color ); /* * get the user's response and restore the mode line. */ c = getkey( ); show_modes( ); if (mode.record == TRUE) { /* * if recording a macro, show recording message */ s_output( main15, g_display.mode_line, 23, g_display.mode_color | 0x80 ); show_avail_strokes( ); } return( c == ESC ? ERROR : OK ); }