static char rcsid[] = "$Id: main.c,v 1.13 1993/08/30 20:15:46 mike Exp $"; /* $Log: main.c,v $ * Revision 1.13 1993/08/30 20:15:46 mike * - Set PL to 7. * * Revision 1.12 1993/06/06 23:22:04 mike * - Set patchlevel to 6. * * Revision 1.11 1993/06/06 22:45:20 mike * - gcc does not define the symbol `ataritt' for TTs anymore, so `mc68020' * is used instead. * * Revision 1.10 1992/12/30 03:15:46 mike * - Set patchlevel to 5. * * Revision 1.9 1992/12/29 00:14:28 mike * - Changed "Bogus key combination" to "Key not bound". * * Revision 1.8 1992/12/28 00:34:16 mike * - Changed format of the version string. * - Changed patch level from 3 to 4. * * Revision 1.7 1992/11/29 01:25:32 mike * - Changed number of Atari version from 2 to 3. * * Revision 1.6 1992/11/14 18:00:48 mike * - Changed number of Atari version from 1 to 2, reason: fixed bug * in `t_beep()' (gemwind.c). * * Revision 1.5 1992/11/11 00:02:14 mike * - If `quit' gets an argument, return it's value to the OS. * Otherwise use `exit(0)'. * * Revision 1.4 1992/11/10 02:21:06 mike * - Changed version string. * * Revision 1.3 1992/11/08 23:29:54 mike * - Added view mode. * * Revision 1.2 1992/10/02 22:31:40 mike * - Some changes for the Atari ST/TT version. * * Revision 1.1 1992/09/05 01:13:32 mike * Initial revision * */ /* main.c * Mutt Editor 2: a screen text editor influenced by Emacs * This program was based on MicroEmacs, orginally written by Dave G. * Conroy and then completely modified/rewritten by Craig Durland. I've * kept most of the core editer structures and concepts because they were * good work. Many thanks to Dave for getting this project rolling. I * hope others will find this work as useful and fun to them as Dave's was * to me. */ /* Copyright 1990, 1991, 1992 Craig Durland * Distributed under the terms of the GNU General Public License. * Distributed "as is", without warranties of any kind, but comments, * suggestions and bug reports are welcome. */ #ifdef atarist #ifdef mc68020 static char what[] = "@(#)ME2 (Mutt Editor II) v2.4/(TT,PL7)"; #else static char what[] = "@(#)ME2 (Mutt Editor II) v2.4/(ST/TT,PL7)"; #endif #else static char what[] = "@(#)ME2 (Mutt Editor II) v2.4 2/2/92"; #endif #define WHAT (&what[4]) #include #include "me2.h" #include "mm.h" #include "stio.h" extern void MMset_hooks(), process_hooks(); static void do_command_line(), edinit(); extern int t_nrow, t_ncol; /* in the terminal dependent code */ char **zargv; /* a copy of argv for MUTT */ int beeper = 100, /* Bell volume. 0 => bell turned off */ doing_nothing = FALSE, /* process control state variable */ kp_hook, /* key pressed hook */ pro_hook, /* external process hook */ thisflag, /* Flags, this command */ lastflag, /* Flags, last command */ zargc; /* a copy of argc for MUTT */ KeyCode key_pressed; /* last key pressed in main loop */ /* Here is where it all happens. ME is initialized and then we sit in * the MainLoop, waiting to be told to do something and doing it. * Notes: * Since this is where the main user interaction takes place, I need * to make sure that the buffers and windows are synchronized and * the display is up to date so that the user sees what I want them * to see. * Every time anything is done, the display probably needs updating. * Every time a Mutt program is run, the current buffer could get * outta sync with the current window. */ main(argc,argv) int argc; char *argv[]; { register KeyCode kc; /* Get everything ready to go */ zargc = argc; zargv = argv; edinit(SCRATCH_BUFFER); do_command_line(argc, argv); /* and maybe run Mutt programs */ lastflag = 0; /* Fake last flags */ while (TRUE) /* The ME main loop */ { /* Sync the current buffer with the current window in case something * changed and to prepare for upcomming changes. * Don't need to change window flags because the current window is * up to date. */ if (curwp->wbuffer == curbp) sync_dot(); else use_window(curwp); /* sync current buffer with current window */ update(); /* Fix up the screen so the user sees whats really there */ /* Wait for somebody to type something. Since we ain't doing * anything, set a flag (incase we get interrupted) saying its OK * to do stuff. Since we are (maybe) going to be waiting, go a * head and process any queued interrupts. Note that getkey() * might also call process_hooks(). */ doing_nothing = TRUE; process_hooks(); /* and maybe run Mutt programs */ #if ATARI kc = events(); /* Handle events and return key */ #else kc = getkey(); /* Waiting for Godot to type */ #endif doing_nothing = FALSE; /* It possible that process_hooks() can run a Mutt program that * leaves the current buffer different than the current window. * Need to resync so if kc runs a Mutt program, the current buffer * will be synced. * Probably should also update if hooks were run??? */ if (curwp->wbuffer == curbp) sync_dot(); else use_window(curwp); if (mline_dirty) /* msg line has text on it */ { mlerase(); if (kc == ' ') continue; /* ITS Emacs does this */ } if (!add_McKey(kc)) continue; /* Save macro key strokes */ if (kp_hook != -1) /* (key-pressed-hook ) */ { extern MMDatum TV; /* in mm.c */ MMStkFrame mark; MMopen_frame(&mark); TV.type = NUMBER; TV.val.num = kc; MMpush_arg(&TV); MMrun_pgm_with_args(kp_hook,&mark); } do_key(kc,FALSE,1); /* and maybe run Mutt programs */ if (!(thisflag & CFCHAR)) /* add sequence point */ save_for_undo(BU_SP,(int32)0); } } /* * Initialize the editor. The buffer name is passed in. */ static void edinit(bname) char *bname; { extern int case_fold; /* in search.c */ MMset_hooks(); /* so no hooks are called before everything is initialized */ /* Set up display stuff */ open_display(); display_resized(t_nrow +1, t_ncol); if ( !osinit() || /* Display, terminal & others */ !mlinit() || /* message line */ !(cut_buffer = alloc_bag(TRUE)) || /* cut buffer */ !buf_init(bname) || /* buffer system */ !win_init() || /* window system */ !initkeys() || /* global key table */ !MMinitialize()) /* Mutt Machine */ { close_display(FALSE); /* restore the display */ exit(1); } fix_cmap(case_fold); } /* Try and load the ME2 Mutt init file. If I can, the the init file * will take over command line processing. If I can't, then I process * the command line. The only thing I do is assume the first thing is * a file name and read it into a buffer. * Input: * argc, argv: just as passed into main() */ static void do_command_line(argc, argv) int argc; char *argv[]; { extern char current_directory[], /* in os.c */ *canonize(); char bname[NFILEN]; /* Load the init file (if it exists) else load the first file. */ if (!MMload("me2.mco",FALSE) && argc > 1) { update(); /* have to update so don't erase msg line */ makename(bname,argv[1]); set_dString(&curbp->b_bname,bname); if (canonize(argv[1], bname, current_directory)) readin(bname); } } /* Hooks for Mutt programs. When certain events occur in ME, an attempt * to call a Mutt program will be made so that ME can be extended in * useful ways. * Notes: * Called from MM after every Mutt code block is loaded. * Call this at start up to make sure all hooks are * properly initialized to no hook. */ void MMset_hooks() /* setup hooks used by ME */ { extern int bc_hook, /* in buffer.c */ fr_hook, /* in file.c */ ml_hook, /* in mline.c */ kp_hook, pro_hook, /* in main.c */ od_hook, cd_hook; /* in display.c */ kp_hook = MMpgm_lookup("key-pressed-hook"); bc_hook = MMpgm_lookup("buffer-created-hook"); fr_hook = MMpgm_lookup("read-file-hook"); ml_hook = MMpgm_lookup("modeline-hook"); od_hook = MMpgm_lookup("enter-ME-hook"); cd_hook = MMpgm_lookup("leave-ME-hook"); pro_hook = MMpgm_lookup("process-hook"); } void view_mode_violation() { extern int MMask_pgm; t_beep(); if (MMask_pgm) { mlwrite("[Program used command illegal in view mode -- aborted]"); MMabort_pgm(1); } else mlwrite("[Command illegal in view mode]"); } /* * This is the general "do a key" routine: * If the is bound, run what it is bound to. * If the key is not bound and it is the "printable" range, treat it * as bound to "self-insert" and insert n of those into the current * buffer at the dot. Do word wrap if need to. * Clear out "thisflag" and to move it to "lastflag", so that the next * command can look at it. * Input: * kc : Keycode to run * f : Flag to send to the command (TRUE if C-u was used). Default * is FALSE. * n : The C-u count. Default is 1. * Returns: * Status of command: TRUE, FALSE or ABORT. * FALSE : Couldn't do anything with the key. */ do_key(kc, f,n) KeyCode kc; /* command character */ int f, /* if argument (^U) used */ n; /* do c n times */ { extern int overstrike; /* in random.c */ int status; key_pressed = kc; thisflag = 0; /* is key bound to anything? */ if (run_key(kc, f,n, &status)) { lastflag = thisflag; return status; } /* If self insert was typed, fill column is defined, argument is * non-negative, are now past fill column and typing at end of line * then perform word wrap. */ if (0x20 <= kc && kc <= 0xFF) { register int wrap_col = curbp->wrap_col; if (n <= 0) { lastflag = 0; return !n; } /* TRUE if n == 0 */ if (curbp->b_flags & BFVIEW) { view_mode_violation(); lastflag = 0; return ABORT; } /* word wrap? */ if (wrap_col > 0 && getccol() > wrap_col && the_dot->offset == llength(the_dot->line)) { wrapword(); /* word wrap is more than a self-insert, don't set thisflag */ } else thisflag = CFCHAR; /* self-insert character(s) */ status = linsert(n,(unsigned char)kc,overstrike); lastflag = thisflag; return status; } /* Don't know what to do with the key */ mlwrite("[Key not bound]"); t_beep(); lastflag = 0; /* Fake last flags since nothing happened */ return FALSE; } /* * Quit command. * If an argument, always quit. Otherwise confirm if a buffer * has been changed and not written out. * Returns the argument as value to the OS. * Normally bound to "C-x C-c". */ quit(f,n) int f,n; { register int s; if (f || /* Argument forces it */ anycb() == FALSE || /* All buffers clean */ (s = mlyesno("Quit")) == TRUE) /* User says it's OK */ { close_display(TRUE); if (f == FALSE) n = 0; #if 0 printf("Return value: %d\n",n); Cconin(); #endif exit(n); } return s; } /* Universal argument * Grap arg from keyboard, run the key. * Bound to "C-u" */ arg_count(f,n) { int mflag = 0; /* -1 => minus, 0 => no key hit, 1 => key hit */ KeyCode c; if (get_McKey(&c)) { n = c; get_McKey(&c); } /* keyboard macro running */ else { if (!f) n = 4; mlwrite("Arg: %d",n); while ('0' <= (c = getkey()) && c <= '9' || c == (CTRL|'U') || c == '-') { if (c == (CTRL|'U')) n *= 4; else if (c == '-') { if (mflag) break; n = 0; mflag = -1; } else { if (mflag == 0) { if (!f) n = 0; mflag = 1; } n = 10*n +c -'0'; } mlwrite("Arg: %d", (mflag >= 0) ? n : (n ? -n : -1)); } if (mflag == -1) { if (n == 0) n++; n = -n; } add_McKey((KeyCode)n); add_McKey(c); /* Save macro key strokes */ } return do_key(c, TRUE,n); /* Do it */ } /* ******************************************************************** */ /* ***************** Keyboard Macros ********************************** */ /* ******************************************************************** */ #define KOFF 0 /* keyboard macro is off */ #define RECORDING 1 /* adding keys to macro */ #define PLAYBACK 2 typedef struct { int McLen; KeyCode McKeys[NKBDM]; } KbdMacro; static KbdMacro kbdm; /* we know McLen is initialized to 0 */ static KeyCode *kptr; static int McLen, McState = KOFF; add_McKey(key) KeyCode key; { if (McState != RECORDING) return TRUE; if (McLen++ > NKBDM-3) { ctrlg(FALSE,0); return FALSE; } *kptr++ = key; return TRUE; } add_McString(str) char *str; { int s; if (McState != RECORDING) return TRUE; while ((s = add_McKey((KeyCode)*str)) && *str++!='\0') ; return s; } /* FALSE => not in a macro or no keys left * TRUE => got keys */ get_McKey(key) KeyCode *key; { if (McState != PLAYBACK || McLen-- <= 0) return FALSE; *key = *kptr++; return TRUE; } get_McString(str) char *str; { KeyCode kc; register int s; if (McState != PLAYBACK) return FALSE; while ((s = get_McKey(&kc)) && (*str++ = kc)) ; return s; } get_McKc(kc,getakey) KeyCode *kc; { if (get_McKey(kc)) return TRUE; /* keyboard macro running */ *kc = getakey ? getkey() : t_getchar(); return add_McKey(*kc); } /* * Begin a keyboard macro. Error if already in a macro. * Bound to "C-x (" */ ctlxlp(f,n) { if (McState != KOFF) { mlwrite("Not now"); return FALSE; } mlwrite("[Start macro]"); McState = RECORDING; kbdm.McLen = 0; /* wipe out old macro in case of (abort) */ kptr = kbdm.McKeys; McLen = 0; return TRUE; } /* * End keyboard macro. * Bound to "C-x )" */ ctlxrp(f,n) { if (McState != RECORDING) { mlwrite("Not now"); return FALSE; } mlwrite("[End macro]"); kbdm.McLen = McLen -1; /* length of macro (not including "C-x )") */ McState = KOFF; return TRUE; } /* * Run a macro. * The command argument is the number of times to loop. Quit as soon as a * command gets an error. Return TRUE if all ok, else FALSE. * !!!This should set thisflag and lastflag! * Bound to "C-x e" */ ctlxe(f,n) { KeyCode key; register int s = TRUE; if (McState != KOFF) { mlwrite("Not now"); return FALSE; } McState = PLAYBACK; while (s == TRUE && 0