mirror of
https://github.com/rfivet/uemacs.git
synced 2024-12-18 07:16:23 -05:00
Renaud Fivet
c10c550bdd
Use assert() and robust fallback instead of exit() to handle 'impossible' cases.
628 lines
14 KiB
C
628 lines
14 KiB
C
/* main.c -- */
|
|
|
|
#define CLRMSG 0 /* space clears the message line with no insert */
|
|
|
|
/*
|
|
* main.c
|
|
*
|
|
* µEMACS 4.2
|
|
*
|
|
* Based on:
|
|
*
|
|
* uEmacs/PK 4.0
|
|
*
|
|
* Based on:
|
|
*
|
|
* MicroEMACS 3.9
|
|
* Written by Dave G. Conroy.
|
|
* Substantially modified by Daniel M. Lawrence
|
|
* Modified by Petri Kutvonen
|
|
*
|
|
* MicroEMACS 3.9 (c) Copyright 1987 by Daniel M. Lawrence
|
|
*
|
|
* Original statement of copying policy:
|
|
*
|
|
* MicroEMACS 3.9 can be copied and distributed freely for any
|
|
* non-commercial purposes. MicroEMACS 3.9 can only be incorporated
|
|
* into commercial software with the permission of the current author.
|
|
*
|
|
* No copyright claimed for modifications made by Petri Kutvonen.
|
|
*
|
|
* This file contains the main driving routine, and some keyboard
|
|
* processing code.
|
|
*
|
|
* REVISION HISTORY:
|
|
*
|
|
* 1.0 Steve Wilhite, 30-Nov-85
|
|
*
|
|
* 2.0 George Jones, 12-Dec-85
|
|
*
|
|
* 3.0 Daniel Lawrence, 29-Dec-85
|
|
*
|
|
* 3.2-3.6 Daniel Lawrence, Feb...Apr-86
|
|
*
|
|
* 3.7 Daniel Lawrence, 14-May-86
|
|
*
|
|
* 3.8 Daniel Lawrence, 18-Jan-87
|
|
*
|
|
* 3.9 Daniel Lawrence, 16-Jul-87
|
|
*
|
|
* 3.9e Daniel Lawrence, 16-Nov-87
|
|
*
|
|
* After that versions 3.X and Daniel Lawrence went their own ways.
|
|
* A modified 3.9e/PK was heavily used at the University of Helsinki
|
|
* for several years on different UNIX, VMS, and MSDOS platforms.
|
|
*
|
|
* This modified version is now called eEmacs/PK.
|
|
*
|
|
* 4.0 Petri Kutvonen, 1-Sep-91
|
|
*
|
|
* This modified version is now called uEMACS.
|
|
*
|
|
* 4.1 Renaud Fivet, 1-May-13
|
|
*
|
|
* Renamed as µEMACS to emphasize UTF-8 support.
|
|
*
|
|
* 4.2 Renaud Fivet, 2015-02-12
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "estruct.h" /* Global structures and defines. */
|
|
#if UNIX
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#include "basic.h"
|
|
#include "bind.h"
|
|
#include "bindable.h"
|
|
#include "buffer.h"
|
|
#include "display.h"
|
|
#include "eval.h"
|
|
#include "execute.h"
|
|
#include "file.h"
|
|
#include "input.h"
|
|
#include "lock.h"
|
|
#include "mlout.h"
|
|
#include "random.h"
|
|
#include "search.h"
|
|
#include "terminal.h"
|
|
#include "termio.h"
|
|
#include "version.h"
|
|
#include "window.h"
|
|
|
|
/* For MSDOS, increase the default stack space. */
|
|
#if MSDOS & TURBO
|
|
#if PKCODE
|
|
extern unsigned _stklen = 20000;
|
|
#else
|
|
extern unsigned _stklen = 32766;
|
|
#endif
|
|
#endif
|
|
|
|
#if UNIX
|
|
static void emergencyexit(int signr)
|
|
{
|
|
quickexit(FALSE, 0);
|
|
quit(TRUE, 0);
|
|
}
|
|
#endif
|
|
|
|
static void edinit( char *bname) ;
|
|
|
|
static void version( void) {
|
|
fputs( PROGRAM_NAME_PFX PROGRAM_NAME_LONG " version " VERSION "\n", stdout) ;
|
|
}
|
|
|
|
|
|
static void usage( void) {
|
|
fputs( "Usage: " PROGRAM_NAME " [OPTION].. [FILE]..\n\n"
|
|
" + start at the end of file\n"
|
|
" +<n> start at line <n>\n"
|
|
" --help display this help and exit\n"
|
|
" --version output version information and exit\n"
|
|
" @cmdfile execute command file\n"
|
|
" -a|A process error file\n"
|
|
" -e|E edit file\n"
|
|
" -g|G<n> go to line <n>\n"
|
|
#if CRYPT
|
|
" -k|K<key> use code key\n"
|
|
#endif
|
|
" -r|R restrictive use\n"
|
|
" -s|S<string> search string\n"
|
|
" -v|V view file\n"
|
|
, stdout) ;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int c = -1; /* command character */
|
|
int f; /* default flag */
|
|
int n; /* numeric repeat count */
|
|
int mflag; /* negative flag on repeat */
|
|
struct buffer *bp; /* temp buffer pointer */
|
|
int firstfile; /* first file flag */
|
|
int carg; /* current arg to scan */
|
|
int startflag; /* startup executed flag */
|
|
struct buffer *firstbp = NULL; /* ptr to first buffer in cmd line */
|
|
int basec; /* c stripped of meta character */
|
|
int viewflag; /* are we starting in view mode? */
|
|
int gotoflag; /* do we need to goto a line at start? */
|
|
int gline = 0; /* if so, what line? */
|
|
int searchflag; /* Do we need to search at start? */
|
|
int saveflag; /* temp store for lastflag */
|
|
int errflag; /* C error processing? */
|
|
bname_t bname ; /* buffer name of file to read */
|
|
#if CRYPT
|
|
int cryptflag; /* encrypting on the way in? */
|
|
ekey_t ekey ; /* startup encryption key */
|
|
#endif
|
|
int newc;
|
|
|
|
#if PKCODE & VMS
|
|
(void) umask(-1); /* Use old protection (this is at wrong place). */
|
|
#endif
|
|
|
|
#if PKCODE & BSD
|
|
sleep(1); /* Time for window manager. */
|
|
#endif
|
|
|
|
#if UNIX
|
|
#ifdef SIGWINCH
|
|
signal(SIGWINCH, sizesignal);
|
|
#endif
|
|
#endif
|
|
if( argc == 2) {
|
|
if( strcmp( argv[ 1], "--help") == 0) {
|
|
usage() ;
|
|
exit( EXIT_SUCCESS) ;
|
|
}
|
|
|
|
if( strcmp( argv[ 1], "--version") == 0) {
|
|
version() ;
|
|
exit( EXIT_SUCCESS) ;
|
|
}
|
|
}
|
|
|
|
/* Initialize the editor. */
|
|
vtinit(); /* Display */
|
|
mloutfmt = mlwrite ;
|
|
edinit("main"); /* Buffers, windows */
|
|
varinit(); /* user variables */
|
|
|
|
viewflag = FALSE; /* view mode defaults off in command line */
|
|
gotoflag = FALSE; /* set to off to begin with */
|
|
searchflag = FALSE; /* set to off to begin with */
|
|
firstfile = TRUE; /* no file to edit yet */
|
|
startflag = FALSE; /* startup file not executed yet */
|
|
errflag = FALSE; /* not doing C error parsing */
|
|
#if CRYPT
|
|
cryptflag = FALSE; /* no encryption by default */
|
|
#endif
|
|
|
|
/* Insure screen is initialized before startup and goto/search */
|
|
update( FALSE) ;
|
|
|
|
/* Parse the command line */
|
|
for (carg = 1; carg < argc; ++carg) {
|
|
/* Process Switches */
|
|
#if PKCODE
|
|
if (argv[carg][0] == '+') {
|
|
gotoflag = TRUE;
|
|
gline = atoi(&argv[carg][1]);
|
|
} else
|
|
#endif
|
|
if (argv[carg][0] == '-') {
|
|
switch (argv[carg][1]) {
|
|
/* Process Startup macroes */
|
|
case 'a': /* process error file */
|
|
case 'A':
|
|
errflag = TRUE;
|
|
break;
|
|
case 'e': /* -e for Edit file */
|
|
case 'E':
|
|
viewflag = FALSE;
|
|
break;
|
|
case 'g': /* -g for initial goto */
|
|
case 'G':
|
|
gotoflag = TRUE;
|
|
gline = atoi(&argv[carg][2]);
|
|
break;
|
|
#if CRYPT
|
|
case 'k': /* -k<key> for code key */
|
|
case 'K':
|
|
cryptflag = TRUE;
|
|
strncpy( ekey, &argv[ carg][ 2], sizeof ekey - 1) ;
|
|
ekey[ sizeof ekey - 1] = 0 ;
|
|
break;
|
|
#endif
|
|
case 'r': /* -r restrictive use */
|
|
case 'R':
|
|
restflag = TRUE;
|
|
break;
|
|
case 's': /* -s for initial search string */
|
|
case 'S':
|
|
searchflag = TRUE;
|
|
strncpy( pat, &argv[ carg][ 2], sizeof pat - 1) ;
|
|
pat[ sizeof pat -1] = 0 ;
|
|
break;
|
|
case 'v': /* -v for View File */
|
|
case 'V':
|
|
viewflag = TRUE;
|
|
break;
|
|
default: /* unknown switch */
|
|
/* ignore this for now */
|
|
break;
|
|
}
|
|
|
|
} else if (argv[carg][0] == '@') {
|
|
|
|
/* Process Startup macroes */
|
|
if (startup(&argv[carg][1]) == TRUE)
|
|
/* don't execute emacs.rc */
|
|
startflag = TRUE;
|
|
|
|
} else {
|
|
|
|
/* Process an input file */
|
|
|
|
/* set up a buffer for this file */
|
|
makename(bname, argv[carg]);
|
|
unqname(bname);
|
|
|
|
/* set this to inactive */
|
|
bp = bfind(bname, TRUE, 0);
|
|
strncpy( bp->b_fname, argv[ carg], sizeof bp->b_fname - 1) ; /* max filename length limited to NFILEN - 1 (79) */
|
|
bp->b_fname[ sizeof bp->b_fname - 1] = 0 ;
|
|
bp->b_active = FALSE;
|
|
if (firstfile) {
|
|
firstbp = bp;
|
|
firstfile = FALSE;
|
|
}
|
|
|
|
/* set the modes appropriatly */
|
|
if (viewflag)
|
|
bp->b_mode |= MDVIEW;
|
|
#if CRYPT
|
|
if (cryptflag) {
|
|
bp->b_mode |= MDCRYPT;
|
|
strncpy( bp->b_key, ekey, sizeof ekey) ;
|
|
cryptbufferkey( bp) ;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if UNIX
|
|
#ifdef SIGHUP
|
|
signal(SIGHUP, emergencyexit);
|
|
#endif
|
|
signal(SIGTERM, emergencyexit);
|
|
#endif
|
|
|
|
/* if we are C error parsing... run it! */
|
|
if (errflag) {
|
|
if (startup("error.cmd") == TRUE)
|
|
startflag = TRUE;
|
|
}
|
|
|
|
/* if invoked with no other startup files,
|
|
run the system startup file here */
|
|
if (startflag == FALSE) {
|
|
startup("");
|
|
startflag = TRUE;
|
|
}
|
|
discmd = TRUE; /* P.K. */
|
|
|
|
/* if there are any files to read, read the first one! */
|
|
bp = bfind("main", FALSE, 0);
|
|
if (firstfile == FALSE && readfirst_f()) {
|
|
swbuffer(firstbp);
|
|
zotbuf(bp);
|
|
} else {
|
|
bp->b_mode |= gmode;
|
|
upmode() ;
|
|
}
|
|
|
|
/* Deal with startup gotos and searches */
|
|
if( gotoflag && searchflag)
|
|
mloutstr( "(Can not search and goto at the same time!)") ;
|
|
else if( gotoflag) {
|
|
if( gotoline( TRUE, gline) == FALSE)
|
|
mloutstr( "(Bogus goto argument)") ;
|
|
} else if( searchflag)
|
|
if( forwhunt( FALSE, 0))
|
|
mloutfmt( "Found on line %d", getcline()) ;
|
|
|
|
/* Setup to process commands. */
|
|
lastflag = 0; /* Fake last flags. */
|
|
|
|
loop:
|
|
/* Execute the "command" macro...normally null. */
|
|
saveflag = lastflag; /* Preserve lastflag through this. */
|
|
execute(META | SPEC | 'C', FALSE, 1);
|
|
lastflag = saveflag;
|
|
|
|
#if TYPEAH && PKCODE
|
|
if (typahead()) {
|
|
newc = getcmd();
|
|
update(FALSE);
|
|
do {
|
|
fn_t execfunc;
|
|
|
|
if (c == newc && (execfunc = getbind(c)) != NULL
|
|
&& execfunc != insert_newline
|
|
&& execfunc != insert_tab)
|
|
newc = getcmd();
|
|
else
|
|
break;
|
|
} while (typahead());
|
|
c = newc;
|
|
} else {
|
|
update(FALSE);
|
|
c = getcmd();
|
|
}
|
|
#else
|
|
/* Fix up the screen */
|
|
update(FALSE);
|
|
|
|
/* get the next command from the keyboard */
|
|
c = getcmd();
|
|
#endif
|
|
/* if there is something on the command line, clear it */
|
|
if (mpresf != FALSE) {
|
|
mloutstr( "") ;
|
|
update(FALSE);
|
|
#if CLRMSG
|
|
if (c == ' ') /* ITS EMACS does this */
|
|
goto loop;
|
|
#endif
|
|
}
|
|
f = FALSE;
|
|
n = 1;
|
|
|
|
/* do META-# processing if needed */
|
|
|
|
basec = c & ~META; /* strip meta char off if there */
|
|
if ((c & META) && ((basec >= '0' && basec <= '9') || basec == '-')) {
|
|
f = TRUE; /* there is a # arg */
|
|
n = 0; /* start with a zero default */
|
|
mflag = 1; /* current minus flag */
|
|
c = basec; /* strip the META */
|
|
while ((c >= '0' && c <= '9') || (c == '-')) {
|
|
if (c == '-') {
|
|
/* already hit a minus or digit? */
|
|
if ((mflag == -1) || (n != 0))
|
|
break;
|
|
mflag = -1;
|
|
} else {
|
|
n = n * 10 + (c - '0');
|
|
}
|
|
if ((n == 0) && (mflag == -1)) /* lonely - */
|
|
mloutstr( "Arg:") ;
|
|
else
|
|
mloutfmt( "Arg: %d", n * mflag) ;
|
|
|
|
c = getcmd(); /* get the next key */
|
|
}
|
|
n = n * mflag; /* figure in the sign */
|
|
}
|
|
|
|
/* do ^U repeat argument processing */
|
|
|
|
if (c == reptc) { /* ^U, start argument */
|
|
f = TRUE;
|
|
n = 4; /* with argument of 4 */
|
|
mflag = 0; /* that can be discarded. */
|
|
mloutstr( "Arg: 4") ;
|
|
while (((c = getcmd()) >= '0' && c <= '9') || c == reptc
|
|
|| c == '-') {
|
|
if (c == reptc)
|
|
if ((n > 0) == ((n * 4) > 0))
|
|
n = n * 4;
|
|
else
|
|
n = 1;
|
|
/*
|
|
* If dash, and start of argument string, set arg.
|
|
* to -1. Otherwise, insert it.
|
|
*/
|
|
else if (c == '-') {
|
|
if (mflag)
|
|
break;
|
|
n = 0;
|
|
mflag = -1;
|
|
}
|
|
/*
|
|
* If first digit entered, replace previous argument
|
|
* with digit and set sign. Otherwise, append to arg.
|
|
*/
|
|
else {
|
|
if (!mflag) {
|
|
n = 0;
|
|
mflag = 1;
|
|
}
|
|
n = 10 * n + c - '0';
|
|
}
|
|
mloutfmt( "Arg: %d", (mflag >= 0) ? n : (n ? -n : -1)) ;
|
|
}
|
|
/*
|
|
* Make arguments preceded by a minus sign negative and change
|
|
* the special argument "^U -" to an effective "^U -1".
|
|
*/
|
|
if (mflag == -1) {
|
|
if (n == 0)
|
|
n++;
|
|
n = -n;
|
|
}
|
|
}
|
|
|
|
/* and execute the command */
|
|
execute(c, f, n);
|
|
goto loop;
|
|
}
|
|
|
|
/*
|
|
* Initialize all of the buffers and windows. The buffer name is passed down
|
|
* as an argument, because the main routine may have been told to read in a
|
|
* file by default, and we want the buffer name to be right.
|
|
*/
|
|
static void edinit(char *bname)
|
|
{
|
|
struct buffer *bp;
|
|
struct window *wp;
|
|
|
|
bp = bfind(bname, TRUE, 0); /* First buffer */
|
|
blistp = bfind("*List*", TRUE, BFINVS); /* Buffer list buffer */
|
|
wp = (struct window *)malloc(sizeof(struct window)); /* First window */
|
|
if (bp == NULL || wp == NULL || blistp == NULL)
|
|
exit(1);
|
|
curbp = bp; /* Make this current */
|
|
wheadp = wp;
|
|
curwp = wp;
|
|
wp->w_wndp = NULL; /* Initialize window */
|
|
wp->w_bufp = bp;
|
|
bp->b_nwnd = 1; /* Displayed. */
|
|
wp->w_linep = bp->b_linep;
|
|
wp->w_dotp = bp->b_linep;
|
|
wp->w_doto = 0;
|
|
wp->w_markp = NULL;
|
|
wp->w_marko = 0;
|
|
wp->w_toprow = 0;
|
|
#if COLOR
|
|
/* initalize colors to global defaults */
|
|
wp->w_fcolor = gfcolor;
|
|
wp->w_bcolor = gbcolor;
|
|
#endif
|
|
wp->w_ntrows = term.t_nrow - 1; /* "-1" for mode line. */
|
|
wp->w_force = 0;
|
|
wp->w_flag = WFMODE | WFHARD; /* Full. */
|
|
}
|
|
|
|
/***** Compiler specific Library functions ****/
|
|
|
|
#if RAMSIZE
|
|
/* These routines will allow me to track memory usage by placing
|
|
a layer on top of the standard system malloc() and free() calls.
|
|
with this code defined, the environment variable, $RAM, will
|
|
report on the number of bytes allocated via malloc.
|
|
|
|
with SHOWRAM defined, the number is also posted on the
|
|
end of the bottom mode line and is updated whenever it is changed.
|
|
*/
|
|
|
|
static void dspram( void) ;
|
|
|
|
#undef malloc
|
|
#undef free
|
|
#if 0
|
|
char *allocate(nbytes)
|
|
/* allocate nbytes and track */
|
|
unsigned nbytes; /* # of bytes to allocate */
|
|
#endif
|
|
void *allocate( size_t nbytes)
|
|
{
|
|
char *mp; /* ptr returned from malloc */
|
|
/* char *malloc(); */
|
|
|
|
mp = malloc(nbytes);
|
|
if (mp) {
|
|
envram += nbytes;
|
|
#if RAMSHOW
|
|
dspram();
|
|
#endif
|
|
}
|
|
|
|
return mp;
|
|
}
|
|
|
|
#if 0
|
|
release(mp)
|
|
/* release malloced memory and track */
|
|
char *mp; /* chunk of RAM to release */
|
|
#endif
|
|
void release( void *mp)
|
|
{
|
|
unsigned *lp; /* ptr to the long containing the block size */
|
|
|
|
if (mp) {
|
|
/* update amount of ram currently malloced */
|
|
lp = ((unsigned *) mp) - 1;
|
|
envram -= (long) *lp - 2;
|
|
free(mp);
|
|
#if RAMSHOW
|
|
dspram();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if RAMSHOW
|
|
static void dspram( void)
|
|
{ /* display the amount of RAM currently malloced */
|
|
char mbuf[20];
|
|
char *sp;
|
|
|
|
TTmove(term.t_nrow - 1, 70);
|
|
#if COLOR
|
|
TTforg(7);
|
|
TTbacg(0);
|
|
#endif
|
|
sprintf(mbuf, "[%lu]", envram);
|
|
sp = &mbuf[0];
|
|
while (*sp)
|
|
TTputc(*sp++);
|
|
TTmove(term.t_nrow, 0);
|
|
movecursor(term.t_nrow, 0);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/* On some primitave operation systems, and when emacs is used as
|
|
a subprogram to a larger project, emacs needs to de-alloc its
|
|
own used memory
|
|
*/
|
|
|
|
#if CLEAN
|
|
|
|
/*
|
|
* cexit()
|
|
*
|
|
* int status; return status of emacs
|
|
*/
|
|
void cexit( int status) {
|
|
struct buffer *bp; /* buffer list pointer */
|
|
struct window *wp; /* window list pointer */
|
|
struct window *tp; /* temporary window pointer */
|
|
|
|
/* first clean up the windows */
|
|
wp = wheadp;
|
|
while (wp) {
|
|
tp = wp->w_wndp;
|
|
free(wp);
|
|
wp = tp;
|
|
}
|
|
wheadp = NULL;
|
|
|
|
/* then the buffers */
|
|
bp = bheadp;
|
|
while (bp) {
|
|
bp->b_nwnd = 0;
|
|
bp->b_flag = 0; /* don't say anything about a changed buffer! */
|
|
zotbuf(bp);
|
|
bp = bheadp;
|
|
}
|
|
|
|
/* and the kill buffer */
|
|
kdelete();
|
|
|
|
/* and the video buffers */
|
|
vtfree();
|
|
|
|
#undef exit
|
|
exit(status);
|
|
}
|
|
#endif
|