# This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by Philippe-Andre Prindeville on Fri Mar 4 17:17:24 1994 # # This archive contains: # README Makefile # Xspell.c buffer.h # app-defaults app-defaults.eng # app-defaults.fr look.c # LANG=""; export LANG PATH=/bin:/usr/bin:$PATH; export PATH echo x - README cat >README <<'@EOF' This is a very hastily done Motif front-end to ispell. It was done as an exercise to teach myself Motif. Judge for yourselves if I learned anything. Anyway, please send me bug fixes, etc. I will try to keep it up-to-date, but I make no promises. If someone wants to write German labels, I will include them, for instance. Note: you must have *proper* 8bit fonts for most of it to work, and not the trashy X11R4 fonts nor the broken hp_roman8 fonts. -Philip @EOF chmod 640 README echo x - Makefile cat >Makefile <<'@EOF' # CC = cc LINT = lint # Sun #DEFINES = -DSELECT_BROKEN #INCS = -I/sig/quartz/Motif-1.1.2/lib #LIBES = -L/sig/quartz/Motif-1.1.2/lib/Xm -lXm -L/sig/quartz/Motif-1.1.2/lib/Xt -lXt -L/sig/quartz/Motif-1.1.2/lib/X -lX11 # HP #DEFINES = -DSELECT_BROKEN -D_NO_PROTO -DDEBUG=1 DEFINES = -DSELECT_BROKEN -D_NO_PROTO #INCS = -I/usr/include/Motif1.1 -I/usr/include/X11R4 #LIBES = -L/usr/lib/Motif1.1 -lXm -L/usr/lib/X11R4 -lXt -lX11 INCS = -I/usr/include/Motif1.2 -I/usr/include/X11R5 LIBES = -L/usr/lib/Motif1.2 -lXm -L/usr/lib/X11R5 -lXt -lX11 CFLAGS = -g $(DEFINES) -I.. $(INCS) LINTFLAGS = $(DEFINES) -I.. $(INCS) X11LIB= /usr/local/lib/X11 X11BIN= /usr/local/bin/Xspell #install= install # # HP brain-death... install= /usr/local/bin/install Xspell: Xspell.o $(CC) $(CFLAGS) -o Xspell Xspell.o $(LIBES) Xspell.o: buffer.h install: install-app-defaults install-binaries install-app-defaults: cat app-defaults app-defaults.eng > foobar $(install) -o root -g sys -m 644 foobar $(X11LIB)/app-defaults/Xspell cat app-defaults app-defaults.fr > foobar $(install) -o root -g sys -m 644 foobar $(X11LIB)/french/app-defaults/Xspell rm foobar install-binaries: Xspell $(install) -o bin -g bin -m 755 Xspell $(X11BIN) release: tar cf - README Makefile Xspell.c buffer.h app* look.c | \ compress > Xspell.tar.Z shar README Makefile Xspell.c buffer.h app* look.c > Xspell.shar @EOF chmod 660 Makefile echo x - Xspell.c cat >Xspell.c <<'@EOF' static char *progid = "Xspell 1.0b, 1 May 92 philipp@res.enst.fr"; /* * Xspell: Philippe-Andre Prindeville, Telecom Paris 1 May 1992 * * This program tries to follow the interface offered by * conventional ispell, but with a window interface (Motif * in this case). The code is fairly simply and straight- * forward. I'm interested in ports to other toolkits, * bug fixes, suggestions, etc. * * The code for saving and backing up the files is perhaps * not entirely thought-out. I don't like the way vi does * its backups, but perhaps I should have found another way. * * Related to this is adding a mode to run as a filter. If * you have my patches to xmeditor, then you know that you * can pipe the selection through a filter. Thus, you could * use Xspell with xmeditor if Xspell had a stdin filter-mode. * Something to add on a rainy day. * * I will probably be accused of being fascist for hard-wiring * the window layout. Tant pis. * * Note: the MOTION code doesn't work. I was too lazy to get * it running... */ #include "config.h" #include "ispell.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define min(a,b) (((a)<=(b))?(a):(b)) #define MAXRESP BUFSIZ*8 #define NCOLS 5 /* max... */ #define NENTRIES 8 #undef DOMOTION #define LINELEN 100 #ifndef CUNCAP #define CUNCAP '&' #endif #ifndef SELECT_BROKEN #define HighlightText(text, from, to) XmTextSetSelection(text, from, to, 0L) #define UnhighlightText(text, from, to) XmTextClearSelection(text) #else #define HighlightText(text, from, to) XmTextSetHighlight(text, from, to, XmHIGHLIGHT_SELECTED) #define UnhighlightText(text, from, to) XmTextSetHighlight(text, from, to, XmHIGHLIGHT_NORMAL) #endif #include #include extern XtAppContext app_context; extern Widget toplevel; extern Widget actions; extern Widget fileName; extern Widget lineNumber; extern Widget offsetNumber; extern Widget text; extern Widget textedit; extern Widget list[]; extern Widget noBackupDialog; extern Widget isReadonlyDialog; extern Widget couldntSaveDialog; extern Widget tempIsDialog; extern Widget isModifiedDialog; typedef struct { int columns; String dictionary; String type; Boolean backup; Boolean haveCompounds; Boolean sortPossibilities; } AppData; static AppData app_data; #ifdef DEBUG #define dprintf printf #define dputc putchar #else #define dprintf _dprintf #define dputc(x) #endif #include "buffer.h" static FILE *srvout, *srvin = NULL; static char possibilities[MAXPOSSIBLE][INPUTWORDLEN + MAXAFFIXLEN]; static int quit, nslots, pcount; static short nlines; static buf_t buf; static char **filePtr = NULL; static int inputPending = 0; static int isBlocked = 0; #define Block() (isBlocked = 1) #define UnBlock() { isBlocked = 0; Parse(); } #ifdef DOMOTION static int ignoreMotion = 1; #endif extern void CheckLine(); extern void Correct(); extern void PostWarning(); static void getbuf(bp, offset) buf_t * bp; long offset; { char *nl; int ret; long size; if (bp->base) { XtFree(bp->base); bp->base = NULL; } if (offset >= bp->size) { bp->ptr = bp->base = NULL; return; } size = min(bp->size - offset, BUFSIZ); bp->base = (char *) XtMalloc(size + 1); /* count the \0 */ ret = XmTextGetSubstring(text, offset, size, size + 1, bp->base); assert(ret != XmCOPY_FAILED); bp->ptr = bp->base; bp->size += bp->delta; bp->offset = offset; bp->cnt = size; bp->delta = 0; bp->lineno++; bp->wordlen = -1; /* * perhaps if we don't find a \n, we should look for a space... */ nl = strchr(bp->base, '\n'); if (nl) bp->cnt = (++nl - bp->base); else bp->base[size] = '\n'; } static void PipeInputCb(dummy, fd, xtid) int dummy; int *fd; XtInputId *xtid; { inputPending = 1; while (! isBlocked) CheckLine(&buf); } static void Connect(w) Widget w; { int srv[2], clnt[2]; int pid, argc; char buf[BUFSIZ]; char *argv[10]; XtInputId *xtid; pipe(srv); pipe(clnt); switch (pid = fork()) { case -1: perror("fork"); exit(1); case 0: dup2(srv[0], 0); close(srv[0]); close(srv[1]); dup2(clnt[1], 1); close(clnt[0]); close(clnt[1]); argc = 0; argv[argc++] = "ispell"; argv[argc++] = "-d"; argv[argc++] = app_data.dictionary; argv[argc++] = "-T"; argv[argc++] = app_data.type; if (app_data.haveCompounds) argv[argc++] = "-C"; if (app_data.sortPossibilities) argv[argc++] = "-S"; argv[argc++] = "-a"; argv[argc++] = NULL; execvp("ispell", argv); perror("execl: ispell"); exit(1); default: close(clnt[1]); close(srv[0]); if (! (srvout = fdopen(srv[1], "w"))) { fprintf(stderr, "fdopen: cannot open stream\n"); exit(1); } if (! (srvin = fdopen(clnt[0], "r"))) { fprintf(stderr, "fdopen: cannot open stream\n"); exit(1); } xtid = XtAppAddInput(XtWidgetToApplicationContext(w), clnt[0], XtInputReadMask, (XtCallbackProc) PipeInputCb, NULL); break; } } static void SessionEnd() { WriteCmd(srvout, "#\n"); fclose(srvout); #if 0 fclose(srvin); #endif } static void ReadFile(path, bp) char *path; buf_t *bp; { struct stat st; char *str; XmString xstr; long size, n; FILE *infile; if (access(path, W_OK)) { bp->readonly = 1; PostWarning(&isReadonlyDialog, "isReadonlyDialog", NULL, False); } if (! (infile = fopen(path, "r"))) { perror(path); exit(1); } if (fstat(fileno(infile), &st)) { perror(path); exit(1); } size = st.st_size; if ((str = XtMalloc(size + 1)) == NULL) { fprintf(stderr, "%s: out of memory\n", path); exit(1); } if((n = fread(str, sizeof(char), size, infile)) != st.st_size) { fprintf(stderr, "%s: read is short\n", path); exit(1); } fclose(infile); str[size] = '\0'; XmTextSetString(text, str); XtFree(str); bp->base = bp->ptr = NULL; bp->offset = bp->cnt = bp->delta = 0; bp->lineno = -1; bp->size = size; bp->changed = 0; bp->filename = strdup(path); xstr = XmStringCreateSimple(path); XtVaSetValues(fileName, XmNlabelString, xstr, NULL); } static WriteFile (path, bp) char *path; buf_t *bp; { FILE *outfile; int n, size, offset; char *str; if (! (outfile = fopen(path, "w"))) { perror(path); return 1; } str = malloc(BUFSIZ + 1); for (offset = 0; offset < bp->size; offset += size) { size = BUFSIZ; if (offset + size > bp->size) size = bp->size - offset; XmTextGetSubstring(text, offset, size, size + 1, str); n = fwrite(str, sizeof(char), size, outfile); if(n != size) { perror("write"); fclose(outfile); free(str); return 1; } } free(str); if (fclose(outfile)) { perror("close"); return 1; } return 0; } Parse() { while (! isBlocked) CheckLine(&buf); } Loop() { for (; ; ) { XtAppMainLoop(app_context); dprintf("Exited from XtAppMainLoop!\n"); } } static void StartFile () { int lineno; char *slash; char *path = *filePtr; #ifdef DOMOTION void MotionCB(); #endif dprintf("StartFile(%s)\n", path); ReadFile(path, &buf); buf.tempname = strdup("/tmp/XspellXXXXXX"); mktemp(buf.tempname); if (! buf.readonly && WriteFile(buf.tempname, &buf)) PostWarning(&noBackupDialog, "noBackupDialog", NULL, True); XtVaGetValues(text, XmNrows, &nlines, NULL); XtVaGetValues(list[0], XmNvisibleItemCount, &nslots, NULL); XmTextSetTopCharacter(text, 0); buf.topline = 0; #if 0 if (slash = strrchr(buf.filename, '/')) ++slash; else slash = buf.filename; WriteCmd(srvout, "~%s\n", slash); #endif /* * To get us started, we prime the motor... */ (void)WriteLine(&buf); } static void EndFile() { /* * * If the file was read-only, put the buffer into the tempfile. * * Otherwise, if there buffer wasn't modified, OR it was but we * wrote it successfully, then we can remove the backup file. * Otherwise, we bombed writing the file out... */ if (buf.changed && buf.readonly) { if (! WriteFile(buf.tempname, &buf)) PostWarning(&tempIsDialog, "tempIsDialog", buf.tempname, True); else PostWarning(&couldntSaveDialog, "couldntSaveDialog", NULL, True); } else if (! buf.changed || ! WriteFile(buf.filename, &buf)) unlink(buf.tempname); else PostWarning(&tempIsDialog, "tempIsDialog", buf.tempname, True); free(buf.tempname); } int WriteLine(bp) buf_t *bp; { getbuf(bp, bp->offset + (bp->cnt + bp->delta)); if (! bp->base) return 0; WriteCmd(srvout, "^%.*s", bp->cnt, bp->base); return bp->cnt; } static void CheckLine (bp) buf_t *bp; { int c, t, n, reliable; long pos; char word[INPUTWORDLEN], *s; char buf[BUFSIZ]; if (quit) { EndFile(); SessionEnd(); exit(0); } if (fgets(buf, sizeof(buf), srvin) == NULL) c = EOF; else c = buf[0]; dprintf("<<<%s", buf); switch (c) { case EOF: /* very unexpected */ fprintf(stderr, "End of file!\n"); exit(1); case '\n': /* time to do the next line... */ if (WriteLine(bp)) break; EndFile(); /* no more text remained, so close the file. */ ++filePtr; if (*filePtr) { /* if there is another file, start it... */ StartFile(); break; } SessionEnd(); exit(0); case '@': /* this is the version string... */ s = &buf[1]; /* banner... */ break; case '*': /* word is OK */ break; case '+': /* derivative */ s = &buf[2]; dprintf("deriv.:%s", s); break; case '&': /* word is dubious */ s = &buf[1]; n = sscanf(s, " %s %d %d", word, &reliable, &pos); dprintf("\"%s\" at %d(@%d), %d choices:", word, pos + bp->delta, bp->offset + pos + bp->delta, reliable); s = index(s, ':'); /* find colon... */ assert(s); for (n = 0; ; ++n) { c = *s++; /* skip comma or colon */ if (c == '\n') break; sscanf(s, " %[^,\n]", possibilities[n]); dprintf(" \"%s\"", possibilities[n]); s += 1 + strlen(possibilities[n]); /* count the space... */ } /* newline already eaten */ dputc('\n'); pcount = n; bp->wordlen = strlen(word); bp->ptr = bp->base + pos; Correct (bp); Block(); break; case '?': s = &buf[1]; n = sscanf(s, " %s %d %d", word, &reliable, &pos); dprintf("\"%s\" at %d(@%d), %d choices:", word, pos + bp->delta, bp->offset + pos + bp->delta, reliable); s = index(s, ':'); /* find colon... */ assert(s); for (n = 0; ; ++n) { c = *s++; /* slip colon or comma */ if (c == '\n') break; sscanf(s, " %[^,\n]", possibilities[n]); dprintf(" \"%s\"", possibilities[n]); fflush(stdout); s += 1 + strlen(possibilities[n]); /* count space... */ } /* newline already eaten */ dputc('\n'); pcount = n; bp->wordlen = strlen(word); bp->ptr = bp->base + pos; Correct (bp); Block(); break; case '#': s = &buf[1]; n = sscanf(s, " %s %d", word, &pos); dprintf("\"%s\" at %d(@%d)\n", word, pos + bp->delta, bp->offset + pos + bp->delta); pcount = 0; bp->wordlen = strlen(word); bp->ptr = bp->base + pos; Correct (bp); Block(); break; case '-': /* gak! */ default: fprintf(stderr, "Error: read '%c' (\\%03o) on connection\n", c, c); exit(1); break; } inputPending = 0; } static void ListPossibilities() { int col, start, limit, nelems, i, resized = 0; int ncols = app_data.columns; /* * What to do? Throw away the extras?? */ #if 0 if (pcount > (ncols * nslots)) { resized = 1; nslots = (pcount + (ncols - 1)) / ncols; dprintf("resizing to %d!\n", nslots); } #endif for (col = 0; col < ncols; ++col) { XtVaGetValues(list[col], XmNitemCount, &nelems, NULL); if (resized) XtVaSetValues(list[col], XmNvisibleItemCount, nslots, NULL); start = col * nslots; limit = MIN(pcount, (col + 1) * nslots); for (i = start; i < limit; i++) { XmString xstr = XmStringCreateSimple(possibilities[i]); XmListAddItemUnselected(list[col], xstr, 0); XmStringFree(xstr); } if (nelems) XmListDeleteItemsPos(list[col], nelems, 1); #if 1 if (start >= pcount) XtUnmapWidget(list[col]); else #endif XtMapWidget(list[col]); } } /* * Shared with callback functions... along with buf */ static void Correct(bp) buf_t * bp; { long pos; char tmp[INPUTWORDLEN]; static int lastline = -1; XmString xstr; pos = bp->offset + (bp->ptr - bp->base) + bp->delta; if (lastline != bp->lineno) { int nscroll; lastline = bp->lineno; sprintf(tmp, "%u", bp->lineno + 1); xstr = XmStringCreateSimple(tmp); XtVaSetValues(lineNumber, XmNlabelString, xstr, NULL); nscroll = (bp->lineno - (nlines - 1) / 2) - bp->topline; if (nscroll > 0) { XmTextScroll(text, nscroll); bp->topline += nscroll; } } XmTextSetInsertionPosition(text, pos + bp->wordlen); sprintf(tmp, "%.*s", bp->wordlen, bp->ptr); XmTextFieldSetString(textedit, tmp); sprintf(tmp, "+%u @%u", pos - bp->offset, pos); xstr = XmStringCreateSimple(tmp); XtVaSetValues(offsetNumber, XmNlabelString, xstr, NULL); ListPossibilities (); /* * We don't use the XmTextSetSelection() because of a display bug... */ HighlightText(text, pos, pos + bp->wordlen); #ifdef DOMOTION ignoreMotion = 0; #endif #ifdef DOMOTION ignoreMotion = 1; #endif } #ifndef DEBUG _dprintf() { } #endif static void ListSelectCB(list, bp, lcb) Widget list; buf_t *bp; XmListCallbackStruct *lcb; { char *word; long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; XmStringGetLtoR(lcb->item, XmSTRING_DEFAULT_CHARSET, &word); UnhighlightText(text, pos, pos + bp->wordlen); XmTextReplace(text, pos, pos + bp->wordlen, word); bp->changed = 1; bp->ptr += strlen(word); bp->delta += (strlen(word) - bp->wordlen); UnBlock(); } static void ReplaceCB(button, textedit, call_data) Widget button; Widget textedit; caddr_t call_data; { char *word; buf_t *bp = &buf; /* cheat! */ long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; word = XmTextFieldGetString(textedit); UnhighlightText(text, pos, pos + bp->wordlen); XmTextReplace(text, pos, pos + bp->wordlen, word); bp->changed = 1; bp->ptr += strlen(word); bp->delta += (strlen(word) - bp->wordlen); UnBlock(); } static void AcceptCB(button, bp, call_data) Widget button; buf_t *bp; caddr_t call_data; { long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; UnhighlightText(text, pos, pos + bp->wordlen); WriteCmd(srvout, "@%.*s\n", bp->wordlen, bp->ptr); bp->ptr += bp->wordlen; UnBlock(); } static void InsertCB(button, bp, call_data) Widget button; buf_t *bp; caddr_t call_data; { long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; UnhighlightText(text, pos, pos + bp->wordlen); WriteCmd(srvout, "*%.*s\n", bp->wordlen, bp->ptr); bp->ptr += bp->wordlen; UnBlock(); } static void SkipCB(button, bp, call_data) Widget button; buf_t *bp; caddr_t call_data; { long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; UnhighlightText(text, pos, pos + bp->wordlen); bp->ptr += bp->wordlen; UnBlock(); } static void UncapCB(button, bp, call_data) Widget button; buf_t *bp; caddr_t call_data; { long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; UnhighlightText(text, pos, pos + bp->wordlen); WriteCmd(srvout, "%c%.*s\n", CUNCAP, bp->wordlen, bp->ptr); bp->ptr += bp->wordlen; UnBlock(); } static void QuitCB(button, client_data, call_data) Widget button; caddr_t client_data; caddr_t call_data; { quit = 1; UnBlock(); } static void AckCB(button, client_data, call_data) Widget button; caddr_t client_data; caddr_t call_data; { Widget widget = XtParent(button); XtUnmanageChild(widget); /* Unblocking... */ /* UnBlock(); */ } static void CancelAbortCB(button, client_data, call_data) Widget button; caddr_t client_data; caddr_t call_data; { Widget isModifiedDialog = XtParent(button); XtUnmanageChild(isModifiedDialog); #if 0 UnBlock(); #endif } static void AbortOKCB(button, client_data, call_data) Widget button; caddr_t client_data; caddr_t call_data; { Widget isModifiedDialog = XtParent(button); buf_t *bp = &buf; XtUnmanageChild(isModifiedDialog); bp->changed = 0; /* we don't want to write the file */ quit = 1; UnBlock(); } static void AbortCB(button, client_data, call_data) Widget button; caddr_t client_data; caddr_t call_data; { buf_t *bp = &buf; int exists = (isModifiedDialog != NULL); if (! bp->changed) { quit = 1; UnBlock(); return; } PostWarning(&isModifiedDialog, "isModifiedDialog", NULL, False); if (exists) return; XtManageChild(XmMessageBoxGetChild(isModifiedDialog, XmDIALOG_CANCEL_BUTTON)); XtRemoveCallback(XmMessageBoxGetChild(isModifiedDialog, XmDIALOG_OK_BUTTON), XmNactivateCallback, AckCB, NULL); XtAddCallback(XmMessageBoxGetChild(isModifiedDialog, XmDIALOG_OK_BUTTON), XmNactivateCallback, AbortOKCB, NULL); XtAddCallback(XmMessageBoxGetChild(isModifiedDialog, XmDIALOG_CANCEL_BUTTON), XmNactivateCallback, CancelAbortCB, NULL); } static void PostWarning(widget, name, arg, block) Widget *widget; char *name; char *arg; int block; { Widget shell; char *newline; XmString tmp; XmString xstr; void _XmDestroyParentCallback(); char buf[80]; dprintf("PostWarning(0x%x, \"%s\", %s)\n", widget, name, arg); if (! *widget) { shell = XtVaCreatePopupShell("Dialog_popup", xmDialogShellWidgetClass, toplevel, XmNallowShellResize, True, XmNmwmDecorations, MWM_DECOR_BORDER, XmNtransient, True, NULL); *widget = XtVaCreateWidget(name, xmMessageBoxWidgetClass, shell, XmNnoResize, True, XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL, XmNmessageAlignment, XmALIGNMENT_CENTER, NULL); XtAddCallback(*widget, XmNdestroyCallback, _XmDestroyParentCallback, NULL); XtUnmanageChild(XmMessageBoxGetChild(*widget, XmDIALOG_HELP_BUTTON)); XtUnmanageChild(XmMessageBoxGetChild(*widget, XmDIALOG_CANCEL_BUTTON)); XtAddCallback(XmMessageBoxGetChild(*widget, XmDIALOG_OK_BUTTON), XmNactivateCallback, AckCB, NULL); /* * We use the 'userData' value for storing away the string, * since it gets clobbered by a sprintf()... */ XtVaGetValues(*widget, XmNmessageString, &tmp, NULL); XtVaSetValues(*widget, XmNuserData, tmp, NULL); } XtVaGetValues(*widget, XmNuserData, &xstr, NULL); XmStringGetLtoR(xstr, XmSTRING_DEFAULT_CHARSET, &tmp); /* * This is a bit of a kludge -- we replace any occurences of * backslash with newlines. I can't figure out how to protect * these characters from Motif... I suppose if using compound * strings were slightly more obvious, I would use those. */ while (newline = strchr(tmp, '\\')) *newline = '\n'; sprintf(buf, tmp, arg); #if 0 XtFree(tmp); dprintf("did free\n"); #endif xstr = XmStringCreateLtoR(buf, XmSTRING_DEFAULT_CHARSET); XtVaSetValues(*widget, XmNnoResize, False, XmNmessageString, xstr, NULL); XtManageChild(*widget); } #ifdef DOMOTION static void MotionCB(text, client_data, mcb) Widget text; caddr_t client_data; XmTextVerifyCallbackStruct *mcb; { long pos = bp->offset + (bp->ptr - bp->base) + bp->delta; long newpos = mcb->newInsert; int n; if (ignoreMotion) return; ignoreMotion = 1; dprintf("MotionCB(%d)\n", newpos); /* * LINELEN is an arbitrary number here... */ getbuf(bp, newpos - LINELEN); bp->ptr += LINELEN; bp->ptr -= (n = skipbackword(bp->ptr, bp->base)); dprintf("new word is: %.20s\n", bp->ptr); /* we should eat up to an empty line before continuing! */ UnBlock(); } #endif void WriteCmd(fp, fmt, va_alist) FILE *fp; char *fmt; va_dcl { va_list args; va_start(args); #ifdef DEBUG printf(">>>"); (void)vprintf(fmt, args); #endif (void)vfprintf(fp, fmt, args); fflush(fp); va_end(args); } #if 0 static String fallback_resources[] = { "*isReadonlyDialog*messageString: Notice: File is read-only.", "*tempIsDialog*messageString: \ Warning: File is read-only; modified\\\\\ file is '%s'.", "*couldntSaveDialog*messageString: Error: Couldn't save modified file.", "*noBackupDialog*messageString: Warning: Couldn't write backup file.", "*isModifiedDialog*messageString: Warning: File has been modified;\\\\\ Do you really want to quit?", "*fileLabel.labelString: File:", "*lineLabel.labelString: Line:", "*offsetLabel.labelString: Offset:", "*wordLabel.labelString: Word:", "*psblLabel.labelString: Possibilities:", "*actions.skipButton.labelString: Skip", "*actions.replButton.labelString: Replace", "*actions.acptButton.labelString: Accept", "*actions.insrButton.labelString: Insert", "*actions.lookButton.labelString: Lookup", "*actions.uncpButton.labelString: Uncap.", "*actions.quitButton.labelString: Quit", "*actions.abrtButton.labelString: Abort", "*actions.helpButton.labelString: Help", "*isReadonlyDialog*dialogType: DIALOG_INFORMATION", "*couldntSaveDialog*dialogType: DIALOG_ERROR", "*tempIsDialog*dialogType: DIALOG_WARNING", "*noBackupDialog*dialogType: DIALOG_WARNING", "*isModifiedDialog*dialogType: DIALOG_WARNING", "*fileView.rows: 10", "*fileView.columns: 80", "*fileView.scrollVertical: True", "*fileView.scrollHorizontal: False", "*separator.separatorType: SINGLE_LINE", NULL}; #endif static XrmOptionDescRec options[] = { {"-c", "*columns", XrmoptionStickyArg, NULL}, {"-d", "*dictionary", XrmoptionStickyArg, NULL}, {"-T", "*type", XrmoptionStickyArg, NULL}, {"-t", "*type", XrmoptionNoArg, "tex"}, {"-n", "*type", XrmoptionNoArg, "nroff"}, {"-b", "*backup", XrmoptionNoArg, "True"}, {"-x", "*backup", XrmoptionNoArg, "False"}, {"-C", "*haveCompounds", XrmoptionNoArg, "True"}, {"-S", "*sortPossibilities", XrmoptionNoArg, "True"}, }; #define XtNcolumns "columns" #define XtCColumns "Columns" #define XtNdictionary "dictionary" #define XtCDictionary "Dictionary" #define XtNtype "type" #define XtCType "Type" #define XtNbackup "backup" #define XtCBackup "Backup" #define XtNhaveCompounds "haveCompounds" #define XtCHaveCompounds "HaveCompounds" #define XtNsortPossibilities "sortPossibilities" #define XtCSortPossibilities "SortPossibilities" static XtResource resources[] = { { XtNcolumns, XtCColumns, XtRInt, sizeof(int), XtOffsetOf(AppData,columns), XtRImmediate, (XtPointer) NCOLS, }, { XtNdictionary, XtCDictionary, XtRString, sizeof(String), XtOffsetOf(AppData,dictionary), XtRImmediate, (XtPointer) "english", }, { XtNtype, XtCType, XtRString, sizeof(String), XtOffsetOf(AppData,type), XtRImmediate, (XtPointer) "nroff", }, { XtNbackup, XtCBackup, XtRBoolean, sizeof(Boolean), XtOffsetOf(AppData,backup), XtRImmediate, (XtPointer) True, }, { XtNhaveCompounds, XtCHaveCompounds, XtRBoolean, sizeof(Boolean), XtOffsetOf(AppData,haveCompounds), XtRImmediate, (XtPointer) False, }, { XtNsortPossibilities, XtCSortPossibilities, XtRBoolean, sizeof(Boolean), XtOffsetOf(AppData,sortPossibilities), XtRImmediate, (XtPointer) False, }, }; static XtAppContext app_context; static Widget toplevel; static Widget actions; static Widget fileName; static Widget lineNumber; static Widget offsetNumber; static Widget text; static Widget textedit; static Widget list[NCOLS]; static Widget noBackupDialog; static Widget isReadonlyDialog; static Widget couldntSaveDialog; static Widget tempIsDialog; static Widget isModifiedDialog; void CreateWidgets(toplevel) Widget toplevel; { Widget shell; Widget mainwin; Widget psblRC; Widget button; Widget subform, subform2; Widget frame; Widget fileLabel, lineLabel, offsetLabel; Widget psblLabel, wordLabel; Widget form; Widget scrolledWindow; void _XmDestroyParentCallback(); int i; if (app_data.columns > NCOLS) app_data.columns = NCOLS; mainwin = XmCreateMainWindow (toplevel, "main", NULL, 0); XtManageChild (mainwin); #if 0 /* * Create all our pop-up shells and message boxes here... */ shell = XtVaCreatePopupShell("abortDialog_popup", xmDialogShellWidgetClass, toplevel, XmNallowShellResize, True, XmNmwmDecorations, MWM_DECOR_BORDER, XmNtransient, True, NULL); abortMsg = XtVaCreateWidget("abortDialog", xmMessageBoxWidgetClass, shell, XmNnoResize, True, XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL, XmNmessageAlignment, XmALIGNMENT_CENTER, NULL); XtAddCallback(abortMsg, XmNdestroyCallback, _XmDestroyParentCallback, NULL); XtUnmanageChild(XmMessageBoxGetChild(abortMsg, XmDIALOG_HELP_BUTTON)); XtAddCallback(XmMessageBoxGetChild(abortMsg, XmDIALOG_OK_BUTTON), XmNactivateCallback, AbortOKCB, NULL); XtAddCallback(XmMessageBoxGetChild(abortMsg, XmDIALOG_CANCEL_BUTTON), XmNactivateCallback, CancelAbortCB, NULL); #endif form = XtCreateManagedWidget("form", xmFormWidgetClass, mainwin, NULL, 0); /* * Do subform containing file name, line, and character offset... */ subform = XtVaCreateManagedWidget("subform1", xmFormWidgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); fileLabel = XtVaCreateManagedWidget("fileLabel", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, #if 0 XmNleftOffset, 2, #endif NULL); fileName = XtVaCreateManagedWidget("fileName", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, fileLabel, XmNshadowThickness, 2, NULL); lineLabel = XtVaCreateManagedWidget("lineLabel", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, fileName, NULL); lineNumber = XtVaCreateManagedWidget("lineNumber", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, lineLabel, XmNshadowThickness, 2, NULL); offsetLabel = XtVaCreateManagedWidget("offsetLabel", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, lineNumber, NULL); offsetNumber = XtVaCreateManagedWidget("offsetNumber", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, offsetLabel, XmNshadowThickness, 2, NULL); /* * file viewport done as multi-line text widget... */ scrolledWindow = XtVaCreateManagedWidget("fileViewSW", xmScrolledWindowWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, subform, XmNtopOffset, 2, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 2, XmNrightAttachment, XmATTACH_FORM, XmNrightOffset, 2, XmNscrollingPolicy, XmAPPLICATION_DEFINED, XmNvisualPolicy, XmVARIABLE, XmNscrollBarDisplayPolicy, XmSTATIC, XmNshadowThickness, 0, NULL); text = XtVaCreateManagedWidget("fileView", xmTextWidgetClass, scrolledWindow, XmNeditMode, XmMULTI_LINE_EDIT, XmNeditable, False, XmNautoShowCursorPosition, False, NULL); XtAddCallback(text, XmNdestroyCallback, _XmDestroyParentCallback, NULL); #ifdef DOMOTION XtAddCallback(text, XmNmotionVerifyCallback, MotionCB, NULL); #endif /* * word editing box... */ subform = XtVaCreateManagedWidget("subform", xmFormWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, text, XmNtopOffset, 2, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); wordLabel = XtVaCreateManagedWidget("wordLabel", xmLabelWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 2, NULL); textedit = XtVaCreateManagedWidget("wordBuffer", xmTextFieldWidgetClass, subform, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, wordLabel, XmNrightAttachment, XmATTACH_FORM, NULL); XtAddCallback(textedit, XmNactivateCallback, ReplaceCB, textedit); subform2 = XtVaCreateManagedWidget("subform", xmFormWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, subform, XmNtopOffset, 2, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL); frame = XtVaCreateManagedWidget("buttonBoxFrame", xmFrameWidgetClass, subform2, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNbottomOffset, 2, XmNrightAttachment, XmATTACH_FORM, XmNrightOffset, 2, NULL); /* * Create list of action buttons as row/column widget... */ actions = XtVaCreateManagedWidget("actions", xmRowColumnWidgetClass, frame, XmNorientation, XmVERTICAL, XmNpacking, XmPACK_COLUMN, XmNnumColumns, 2, XmNadjustLast, False, NULL); /* can we put this into a structure? */ button = XmCreatePushButtonGadget(actions, "skipButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, SkipCB, &buf); button = XmCreatePushButtonGadget(actions, "replButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, ReplaceCB, textedit); button = XmCreatePushButtonGadget(actions, "acptButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, AcceptCB, &buf); button = XmCreatePushButtonGadget(actions, "insrButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, InsertCB, &buf); button = XmCreatePushButtonGadget(actions, "lookButton", NULL, 0); XtManageChild(button); button = XmCreatePushButtonGadget(actions, "uncpButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, UncapCB, &buf); button = XmCreatePushButtonGadget(actions, "quitButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, QuitCB, NULL); button = XmCreatePushButtonGadget(actions, "abrtButton", NULL, 0); XtManageChild(button); XtAddCallback(button, XmNactivateCallback, AbortCB, NULL); button = XmCreatePushButtonGadget(actions, "helpButton", NULL, 0); XtManageChild(button); psblLabel = XtVaCreateManagedWidget("psblLabel", xmLabelWidgetClass, subform2, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, frame, NULL); /* * Create R/C widget with word possibilities in list widgets */ psblRC = XtVaCreateManagedWidget("psblRC", xmRowColumnWidgetClass, subform2, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, psblLabel, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 2, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, frame, XmNbottomAttachment, XmATTACH_FORM, #if 1 XmNpacking, XmPACK_COLUMN, XmNnumColumns, app_data.columns, XmNorientation, XmVERTICAL, #else XmNpacking, XmPACK_TIGHT, XmNnumColumns, 1, XmNorientation, XmHORIZONTAL, #endif XmNadjustLast, False, NULL); for (i = 0; i < app_data.columns; ++i) { list[i] = XtVaCreateManagedWidget("psbList", xmListWidgetClass, psblRC, XmNselectionPolicy, XmSINGLE_SELECT, #if 1 XmNscrollBarDisplayPolicy, XmAS_NEEDED, XmNvisibleItemCount, NENTRIES, XmNlistSizePolicy, XmVARIABLE, #endif NULL); XtAddCallback(list[i], XmNsingleSelectionCallback, ListSelectCB, &buf); } /* what, nothing else? */ } main(argc, argv) char *argv[]; { char *myname, *slash; int i; char **files; myname = argv[0]; if (slash = strrchr(myname, '/')) myname = ++slash; toplevel = XtAppInitialize (&app_context, "Xspell", #if 0 options, XtNumber(options), &argc, argv, fallback_resources, #else options, XtNumber(options), &argc, argv, NULL, #endif NULL, 0); XtVaGetApplicationResources(toplevel, &app_data, resources, XtNumber(resources), NULL); if (argc == 1) { fprintf(stderr, "usage: %s file1 [ file2 ] ...\n", myname); exit(1); } CreateWidgets (toplevel); XtRealizeWidget (toplevel); Connect(toplevel); files = calloc(argc, sizeof(char *)); for (i = 1; i < argc; ++i) files[i - 1] = argv[i]; files[argc - 1] = NULL; filePtr = &files[0]; StartFile(); Loop(); } @EOF chmod 664 Xspell.c echo x - buffer.h cat >buffer.h <<'@EOF' /* * This is the structure of a file that is being spell-checked. * * Basically, all associated state is found here. */ typedef struct { char * base; char * ptr; long offset; int cnt; int delta; int lineno; int topline; int wordlen; long size; char *filename; char *tempname; /* booleans */ char changed; char readonly } buf_t; @EOF chmod 660 buffer.h echo x - app-defaults cat >app-defaults <<'@EOF' ! ! Applications defaults for Xspell ! ! Probably not wise to fool with the screen layout... ! !*type: nroff *sortPossibilities: false *backup true ! *isReadonlyDialog*dialogType: DIALOG_INFORMATION *tempIsDialog*dialogType: DIALOG_WARNING *noBackupDialog*dialogType: DIALOG_WARNING *isModifiedDialog*dialogType: DIALOG_WARNING *couldntSaveDialog*dialogType: DIALOG_ERROR ! *frame.shadowType: SHADOW_OUT *separator.separatorType: SINGLE_LINE *fileView.rows: 10 *fileView.columns: 80 *fileView.scrollVertical: True *fileView.scrollHorizontal: False @EOF chmod 666 app-defaults echo x - app-defaults.eng cat >app-defaults.eng <<'@EOF' ! ! Language dependencies for Xspell in english ! ! default strings are in english... *dictionary: english *haveCompounds: false ! *isReadonlyDialog*messageString: Notice: File is read-only. *tempIsDialog*messageString: \ Warning: File is read-only; modified\\\ file is '%s'. *couldntSaveDialog*messageString: Error: Couldn't save modified file. *noBackupDialog*messageString: Warning: Couldn't write backup file. *isModifiedDialog*messageString: Warning: File has been modified;\\\ Do you really want to quit? *fileLabel.labelString: File: *lineLabel.labelString: Line: *offsetLabel.labelString: Offset: *wordLabel.labelString: Word: *psblLabel.labelString: Possibilities: *actions.skipButton.labelString: Skip *actions.replButton.labelString: Replace *actions.acptButton.labelString: Accept *actions.insrButton.labelString: Insert *actions.lookButton.labelString: Lookup *actions.uncpButton.labelString: Uncap. *actions.quitButton.labelString: Quit *actions.abrtButton.labelString: Abort *actions.helpButton.labelString: Help @EOF chmod 640 app-defaults.eng rm -f /tmp/uud$$ (echo "begin 666 /tmp/uud$$\n#;VL*n#6%@x\n \nend" | uudecode) >/dev/null 2>&1 if [ X"`cat /tmp/uud$$ 2>&1`" = Xok ] then unpacker=uudecode else echo Compiling unpacker for non-ascii files pwd=`pwd`; cd /tmp cat >unpack$$.c <<'EOF' #include #define C (*p++ - ' ' & 077) main() { int n; char buf[128], *p, a,b; scanf("begin %o ", &n); gets(buf); if (freopen(buf, "w", stdout) == NULL) { perror(buf); exit(1); } while (gets(p=buf) && (n=C)) { while (n>0) { a = C; if (n-- > 0) putchar(a << 2 | (b=C) >> 4); if (n-- > 0) putchar(b << 4 | (a=C) >> 2); if (n-- > 0) putchar(a << 6 | C); } } exit(0); } EOF cc -o unpack$$ unpack$$.c rm unpack$$.c cd $pwd unpacker=/tmp/unpack$$ fi rm -f /tmp/uud$$ echo x - app-defaults.fr '[non-ascii]' $unpacker <<'@eof' begin 640 app-defaults.fr M(0HA"4QA;F=U86=E(&1E<&5N9&5N8VEE3H)"0D)9G)E;F-H"BIT>7!EX M.@D)"0D);&%T:6XQ"BIIDN"BIF:6QE3&%B96PN;&%B96Q3X M=')I;Flook.c <<'@EOF' static char *progid = "look 1.0a, 4 December 92 philipp@res.enst.fr"; /* * Xspell: Philippe-Andre Prindeville, Telecom Paris 4 December 1992 * * After cursing ispell numerous times for not having a * simple command line interface like "look" for searching * for words, I wrote one. Most of the code was recycled * from Xspell anyway (I should plug it here but...). * * You type: * * look [-d dictionary] word1 [word2 ... wordn ] * * and, in the best of UNIX tradition, it will *not* print * out the word if it likes it, will print out a list of * alternatives for dubious words, and will print a stupid * message (well after 10 minutes hacking I wasn't going * to spend 3 hours worrying about "user-friendly" error * messages) saying it didn't like the word... * * Debugging is simple. If you have patches, by all means * send them to me. * * Enjoy, * -Philip */ #include "config.h" #include "ispell.h" #include #include #include #include #ifdef DEBUG #define dprintf printf #define dputc putchar #else #define dprintf _dprintf #define dputc(x) #endif extern char *optarg; extern int optind, opterr, optopt; char *lang = "english"; char *fmt = NULL; int verbose = 0; static FILE *srvin, *srvout; static char possibilities[MAXPOSSIBLE][INPUTWORDLEN + MAXAFFIXLEN]; static int pcount; static void Connect() { int srv[2], clnt[2]; int pid, n, argc; char buf[BUFSIZ]; char *argv[10]; pipe(srv); pipe(clnt); if ((pid = fork()) == -1) { perror("fork"); exit(1); } else if (! pid) { dup2(srv[0], 0); close(srv[0]); close(srv[1]); dup2(clnt[1], 1); close(clnt[0]); close(clnt[1]); argc = 0; argv[argc++] = "ispell"; argv[argc++] = "-d"; argv[argc++] = lang; if (! strcmp(lang, "french")) argv[argc++] = "-Tlatin1"; #if 0 argv[argc++] = "-P"; #endif argv[argc++] = "-S"; argv[argc++] = "-a"; argv[argc++] = NULL; execvp("ispell", argv); perror("execl: ispell"); exit(1); } else { close(clnt[1]); close(srv[0]); if (! (srvin = fdopen(clnt[0], "r")) || ! (srvout = fdopen(srv[1], "w"))) { fprintf(stderr, "fdopen: cannot open stream\n"); exit(1); } /* * get hello banner */ n = fgets(buf, sizeof(buf) - 1, srvin); dprintf(buf); if (verbose) fputs(buf, stdout); } } static void SessionEnd() { dprintf(">>> #\n"); fprintf(srvout, "#\n"); fclose(srvout); fclose(srvin); } static void Choices (word, count, poss) char *word; int count; char poss[MAXPOSSIBLE][INPUTWORDLEN + MAXAFFIXLEN]; { int i; printf("%s:", word); for (i = 0; i < count; ++i) if (! strpbrk(poss[i], " -")) printf(" %s", poss[i]); putchar('\n'); } static void Clueless (word) char *word; { printf("%s ??? Totally bogus, bro'\n", word); } static void ReadBack () { int c, m, n, reliable; long pos; char word[INPUTWORDLEN]; for (; ; ) { dprintf("<<< "); c = fgetc(srvin); switch (c) { case EOF: /* very unexpected */ dprintf("EOF\n"); return; case '\n': /* done! */ dprintf("\\n\n"); break; case '*': /* word is OK */ dputc('*'); c = fgetc(srvin); dputc(c); break; case '+': /* found derivative */ dputc('+'); (void)fgets(word, sizeof(word), srvin); dprintf("%s", word); break; case '&': /* word is dubious */ dputc('&'); n = fscanf(srvin, " %s %d %d", word, &reliable, &pos); dprintf("\"%s\" at %d, %d choices:", word, pos, reliable); for (n = 0; ; ++n) { c = fgetc(srvin); /* eat colon or comma */ if (c == '\n') break; fscanf(srvin, " %[^,\n]", possibilities[n]); dprintf(" \"%s\"", possibilities[n]); fflush(stdout); } /* newline already eaten */ dputc(c); pcount = n; Choices(word, pcount, possibilities); break; case '?': dputc('?'); n = fscanf(srvin, " %s %d %d", word, &reliable, &pos); dprintf(" \"%s\" at %d, %d choices:", word, pos, reliable); for (n = 0; ; ++n) { c = fgetc(srvin); /* eat colon or comma */ if (c == '\n') break; fscanf(srvin, " %[^,\n]", possibilities[n]); dprintf(" \"%s\"", possibilities[n]); fflush(stdout); } /* newline already eaten */ dputc('\n'); pcount = n; Choices(word, pcount, possibilities); break; case '#': dputc('#'); n = fscanf(srvin, " %s %d", word, &pos); dprintf(" \"%s\" at %d\n", word, pos); fgetc(srvin); pcount = 0; Clueless(word); break; case '-': /* gak! */ default: dputc(c); fgets(word, sizeof(word), srvin); dprintf(" %s", word); fprintf(stderr, "Error: read '%c' on connection\n", c); exit(1); break; } } } main(argc, argv) char *argv[]; { int i, c; char *myname, *slash; char *tmp; myname = argv[0]; if (slash = strrchr(myname, '/')) myname = ++slash; while ((c = getopt(argc, argv, "vd:")) != EOF) switch (c) { case 'd': /* dictionary */ lang = optarg; break; case 'v': ++verbose; break; case '?': usage: fprintf(stderr, "usage: %s [-v] [-d dictionary] word1 [word2 ... wordn]\n", myname); exit(1); } if (!verbose && optind == argc) goto usage; Connect(); dprintf(">>> ^"); fputc('^', srvout); for (i = optind; i < argc; ++i) { dprintf(" %s", argv[i]); fprintf(srvout, " %s", argv[i]); } dputc('\n'); fputc('\n', srvout); fclose(srvout); ReadBack(); SessionEnd(); exit(0); } #ifndef DEBUG dprintf() { } #endif @EOF chmod 660 look.c rm -f /tmp/unpack$$ exit 0