/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ #include "jove.h" RCS("$Id: misc.c,v 14.31.0.9 1994/02/01 20:35:18 tom Exp tom $") #include "ctype.h" #include "io.h" #include "maps.h" #ifdef ANSICODES # include "termcap.h" #endif /* * [TRH] FourTime, Digit are revised completely. * - GetExp is turned into a simple 'calculator': * 0-9 add digit * - toggle sign * X4 times 4 (setup by quadruple) * / undo last keystroke * - validation check moved to Digit() * Note: FourTime should not be bound to * a digit, minus sign, or slash * [Jul 90] * make quadruple behave more like "standard" emacs, i.e., reset counter * if you switch from quadruple => Digit. (but only if you started out * with quadruple. Otherwise just multiply current argument by 4) */ int exp_p, exp; DEF_CMD( "-", Digit, ARG('-') ); DEF_CMD( "0", Digit, ARG('0') ); DEF_CMD( "1", Digit, ARG('1') ); DEF_CMD( "2", Digit, ARG('2') ); DEF_CMD( "3", Digit, ARG('3') ); DEF_CMD( "4", Digit, ARG('4') ); DEF_CMD( "5", Digit, ARG('5') ); DEF_CMD( "6", Digit, ARG('6') ); DEF_CMD( "7", Digit, ARG('7') ); DEF_CMD( "8", Digit, ARG('8') ); DEF_CMD( "9", Digit, ARG('9') ); DEF_CMD( "quadruple-numeric-argument", Digit, ARG('*') ); DEF_CMD( "digit", Digit, ARG(0) ) { register int c, num = 0, /* suitable defaults */ sign = 1, digited = 0, X4 = -1; #ifndef TINY static int base; #else # define base 10 #endif if ((c = ObjArg(LastCmd)) == 0) { /* "digit" */ c = LastKeyStruck; if (!isdigit(c) && c != '-') complain((char *)0); } else if (c == '*') /* "quadruple" */ X4 = c = LastKeyStruck; switch (exp_p) { case YES: digited++; if ((num = exp) < 0) { num = -num; sign = -sign; } break; default: /* YES_NODIGIT */ if (exp < 0) sign = -sign; break; case NO: #ifndef base base = 10; #endif break; } for (;;) { if ((unsigned)(c -= '0') < 10) { /* add digit */ /* * if we switch from quadruple to digit FOR THE FIRST * TIME, reset the counter. * Switch from decimal to octal if the first digit * typed is `0'. */ if (((X4 >= 0) && (X4 = -1, !exp_p)) || ((c += num * base) / base == num)) /* overflow? */ #ifndef base if ((num = c) == 0) /* nope */ base = 8; #else num = c; #endif digited++; } else if ((c += '0') == X4) { /* times four */ if (num == 0) num = 4; else if ((c = num << 2) >> 2 == num) /* overflow? */ num = c; /* nope */ digited++; } else if (c == '-') { /* toggle sign */ sign = -sign; } else if (c == '/') { /* delete keystroke */ if (num == 0) #ifndef base base = 10, #endif digited = 0, sign = 1; /* reset */ else num /= (X4 >= 0) ? 4 : base; } else break; repl_strokes(sprint((!digited) ? "%c " : #ifndef base (base == 8 && num != 0) ? "%c0%o " : #endif "%c%d ", (sign < 0) ? '-' : '+', num)); c = waitchar(); } Ungetc(c); this_cmd = ARG_CMD; if (digited) { exp_p = YES; exp = sign * num; } else { exp_p = YES_NODIGIT; exp = sign; } #undef base } /* Supply a programmable default value for numeric argument */ void DefExp(pval) register int *pval; { if (exp_p) { /* remember it */ *pval = 0; if (exp_p == YES) if ((*pval = exp) < 0) *pval = -*pval; } else if (*pval) /* restore from remembered value */ exp_p = YES, exp *= *pval; } DEF_CMD( "date", prCTIME, NO ) { register const char *timestr = get_ctime(); if (exp < 0) ins_str(timestr, NO); else s_mess(ProcArgFmt, timestr); } /* [TRH] This one was missing - rather nice for macros */ DEF_CMD( "print-message", PrintMesg, NO ) { register Frame *msp; register const char *mesg; if (msp = MacStkPtr) LastCmd = (data_obj *) msp->mf_macro; /* a convenient lie... */ mesg = ask(NullStr, ProcFmt); if (exp < 0) ins_str(mesg, NO); else f_mess("%s", mesg); } DEF_CMD( "simulate-input", SimInput, NO ) { Inputp = ask((char *) 0, ProcFmt); } #ifndef TINY DEF_CMD( "character-to-octal-insert", NonExisting, NO ); _IF(def TINY) DEF_CMD( "character-to-octal-insert", ChrToOct, EDIT ) _IF(ndef TINY) { register int c; /* [TRH 9-Jan-91] now converts character AT point (was BEFORE). */ if (exp < 0 && (c = linebuf[curchar])) { exp = 1, DelNChar(); /* remove it */ } else { s_mess(ProcFmt); c = addgetc(); if (c == QuoteChar) /* [TRH] my 8-bit kludge ... */ c = addgetc(); } ins_str(sprint(exp_p == YES ? "\\x%02x" : "\\%03o", c), NO); } #endif /* Transpose cur_char with cur_char - 1 */ DEF_CMD( "transpose-characters", TransChar, EDIT ) { register int num = curchar; register char *cp = &linebuf[num]; if (*cp == '\0') --cp, --num; if (num <= 0) complain((char *) 0); /* BEEP */ if ((num = exp) <= 0) { #if/*def TINY*/ 0 return; #else register int at; if ((num = -num) == 0) return; at = (cp - linebuf); if (num > at) num = at; #endif } do { register char before = cp[-1]; if (before != cp[0]) { modify(); makedirty(curline); } cp[-1] = cp[0]; *cp++ = before; #if/*ndef TINY*/ 1 if (exp < 0) cp -= 2; #endif } while (*cp && --num); curchar = cp - linebuf; } /* Switch current line with previous one [TRH] now accepts a (positive or negative) argument */ DEF_CMD( "transpose-lines", TransLines, EDIT ) { register int num; register Line *this = curline, *prev; if ((prev = this->l_prev) == NULL) return; if ((num = exp) <= 0) { if ((num = -num) == 0) return; } lsave(); do { register disk_line da = this->l_dline; modify(); this->l_dline = prev->l_dline; prev->l_dline = da; if (exp < 0) { /* BACKWARD */ this = prev; if ((prev = prev->l_prev) == NULL) break; } else { /* FORWARD */ if (this->l_next == NULL) break; prev = this; this = this->l_next; } } while (--num); getDOT(); /* since we mucked about with disk_lines. */ SetLine(this); } void put_bufs(askp, advance) { register Buffer *b; register const char *fname; register Buffer *oldb = curbuf; if (b = world) do { if (!IsModified(b) || b->b_type != B_FILE) continue; if ((fname = b->b_fname) == NULL) { /* do_ask so you can still get default with C-R */ if ((fname = do_ask("\r\n", (int (*)()) 0, b->b_name, "Buffer \"%b\" needs a file name; type Return to skip: ", b)) == NULL) continue; setfname(b, fname); } if (askp && yes_or_no_p('Y', "Write %s? ", fname) == NO) continue; SetBuf(b); /* Make this current Buffer */ SaveFile(); /* ... so this'll work */ /* Kludge for "quick-exit" to show all file sizes. */ if (advance) { DrawMesg(NO); /* flush message buf */ printf("\n"); /* next line */ f_mess(NullStr); /* clear message line */ } } while (b = b->b_next); SetBuf(oldb); } DEF_CMD( "write-modified-files", WtModBuf, NO ) { /* default message will be overwritten when any buffer is saved */ message("[No buffers need saving]"); put_bufs((exp_p & YES), NO); } void skip_blanks() { register Buffer *cb = curbuf; register char *cp = &linebuf[cb->b_char]; while (isspace(*cp++)); cb->b_char = cp - linebuf - 1; } DEF_CMD( "first-non-blank", ToIndent, NO ) { Bol(); skip_blanks(); } void to_line(lineno) { register Buffer *cb = curbuf; register int lno; register Line *start = cb->b_last; if ((lno = lineno) > 0) { --lno; start = cb->b_first; } SetLine(next_line(start, lno)); } DEF_CMD( "goto-line", GoLine, NO ) { register Buffer *cb = curbuf; register Line *newdot = cb->b_dot; #ifdef ANSICODES if (SP && exp_p == NO) { putp(SP); /* Query cursor position */ return; } #endif if (exp <= 0) newdot = cb->b_last; newdot = next_line(cb->b_first, exp_ask_int(lineno(newdot)) - 1); PushPntp(newdot); SetLine(newdot); } #ifndef TINY DEF_CMD( "goto-character", NonExisting, NO ); _IF(def TINY) DEF_CMD( "goto-character", GoChar, NO ) _IF(ndef TINY) { Bufpos pos[2]; register Bufpos *oldpos = &pos[0], *newpos = &pos[1]; if (!exp_p) exp = ask_int(ProcFmt, 10, 0); DOTsave(oldpos); if (exp < 0) ToLast(); else ToFirst(); ForChar(); DOTsave(newpos); SetDot(oldpos); PushPntp(newpos->p_line); SetDot(newpos); } DEF_CMD( "help", NonExisting, NO ); _IF(def TINY) DEF_CMD( "help", Help, SPARSE|ARG(HELPMAP_INDEX) ) _IF(ndef TINY) { # ifndef FIXED_MAPS register sparsemap *HelpMap = (sparsemap *)Maps[ObjArg(LastCmd)].k_bind; # endif /* create the prompt... */ findsparse(HelpMap, -1); ExecCmd(findsparse(HelpMap, addgetc())); } #endif /* TINY */ #ifdef ANSICODES private void MoveToCursor __(( int _(line), int _(col) )); private void MoveToCursor(line, col) int line, col; { register struct scrimage *sp = &PhysScreen[line]; while (sp->s_id == NULL) --sp; if (sp->s_flags & MODELINE) complain(NULL); if (curwind != sp->s_window) SetWind(sp->s_window); SetLine(sp->s_lp); curchar = how_far(linebuf, col); } DEF_CMD( "ansi-codes", AnsiCodes, NO ) _IF(def ANSICODES) { register int c; register int num1 = 0; register int num2; while (isdigit(c = getch())) num1 = (num1*10) + (c - '0'); switch (c) { case ';': num2 = 0; while (isdigit(c = getch())) num2 = (num2*10) + (c - '0'); switch (c) { case 'R': MoveToCursor(--num1, --num2); return; case 'H': Eow(); Bol(); return; } break; case 'A': exp = -exp; case 'B': ForwLine(); return; case 'D': exp = -exp; case 'C': ForChar(); return; case 'H': Bow(); return; case 'J': if (num1 == 2) { ClAndRedraw(); return; } /* FALL THROUGH */ } complain("[Unsupported ANSI code received]"); /* NOTREACHED */ } #endif /* ANSICODES */ DEF_CMD( "left-margin-here", SetMargin, ARG(0) ); DEF_CMD( "right-margin-here", SetMargin, ARG(1) ) { register int margin = calc_pos(linebuf, curchar); /* Don't bother to check validity here since you can screw them anyhow with explicit "set {left,right}-margin" commands. */ if (LastCmd->Type & ARG(1)) RMargin = margin; else LMargin = margin; #ifndef TINY s_mess(": %f => %d", margin); #endif } #ifndef TINY /* [TRH] * Recursively edit internal data structures (abbreviations, macros, ...) * via temporary files. save and restore are pointers to routines to * save, resp. restore the data structure to a file. These are called * with a single parameter (file name). * Name is a pointer to the desired buffer name. * (it is also used as the name of the temp. file) * * Code snatched from abbreviation edit. */ void EditParam(save, restore, name) void (*save)__(( const char *_(file) )), (*restore)__(( const char *_(file) )); register const char *name; { char org_bname[BNAMESIZE]; /* name of original buffer */ register Buffer *editbuf; /* If arbitrary-length filenames are allowed, use `name' as tmp-file name in current directory. This way {read,write}_file() display a nice file name. However it fails when current directory is not writeable, so you can call this rightly a kludge. */ #ifndef _POSIX_NO_TRUNC # ifndef _PC_NO_TRUNC /* configurable maybe? */ # ifndef SCO_SYSV # define tmpfile name # endif # endif #else # if (_POSIX_NO_TRUNC != -1) /* long filenames are truncated. */ # define tmpfile name # endif #endif #ifndef tmpfile char tnamebuf[FILESIZE]; register const char *tmpfile; # ifdef _PC_NO_TRUNC tmpfile = name; if (pathconf(".", _PC_NO_TRUNC) >= 0 && pathconf(".", _PC_NAME_MAX) < strlen(tmpfile)) # endif tmpfile = mktemp(make_filename(tnamebuf, TmpFilePath, "jeditXXXXXX")); #endif if ((editbuf = buf_exists(name)) && editbuf->b_type != B_SCRATCH) { confirm("Over-write buffer %b? ", editbuf); editbuf->b_modified = 0; } /* save current buffer's NAME since buffer itself can be deleted */ strcpy(org_bname, curbuf->b_name); SetBuf(editbuf = do_select(curwind, name)); SETBUFTYPE(editbuf, B_SCRATCH); /* if you changed the buffer beforehand, don't destroy these changes */ if (!IsModified(editbuf)) { (*save)(tmpfile); /* save your stuff */ read_file(tmpfile, NO); /* ...and load it into the buffer */ unlink(tmpfile); } s_mess("[%s and then type C-X C-C]", name); Recur(); /* edit them... */ /* Recur() takes care that original buffer (ie. editbuf) is selected */ if (IsModified(editbuf)) { write_file(tmpfile, NO); (*restore)(tmpfile); unlink(tmpfile); } SetBuf(do_select(curwind, org_bname)); #undef tmpfile } #endif /* TINY */ /* [TRH] from extend.c which was getting too big for my 7th Ed. compiler... */ DEF_CMD( "buffer-position", BufPos, NO ) { register Buffer *cb = curbuf; register Line *lp = cb->b_first; register int nlines = 0, dotline; register long dotchar, nchars = 0; register int dotc; #ifdef CRLF /* report internal character count if argument is given, */ long NL_LEN = (exp_p || True(BinWrtFile)) ? 1 : 2; #else # define NL_LEN 1 #endif do { nlines++; if (lp == cb->b_dot) { dotchar = nchars + cb->b_char; dotline = nlines; } nchars += length(lp) + NL_LEN; /* include the NL */ } while (lp = lp->l_next); if (exp_p || False(EndWNewline) || length(cb->b_last) == 0) nchars -= NL_LEN; /* one newline too far */ s_mess("\"%s\" line %d of %d, char %D of %D (%d%%), Point %d of %d", filename(cb), dotline, nlines, dotchar, nchars, (nchars) ? (int) ((dotchar * 100) / nchars) : 100, calc_pos(linebuf, cb->b_char), calc_pos(linebuf, strlen(linebuf)) ); #ifndef TINY if ((dotc = linebuf[cb->b_char]) == '\0') dotc = '\n'; /* end-of-line, so cheat */ # ifdef NULLCHARS else if (dotc == '\n') /* '\n' internally used to represent '\0' */ dotc = '\0'; if (isctrl(dotc)) add_mess(" (^%c=%d)", CTL(dotc), dotc); else # endif add_mess(" (%c=%d)", dotc, dotc); #endif #undef NL_LEN } /* Read a positive integer from CP. It must be in base BASE (<= 10), and complains if it isn't. If allints is nonzero, all the characters in the string must be integers or we return -1; otherwise we stop reading at the first nondigit. */ int chr_to_int(cp, base, allints) register const char *cp; { register int c; register int value = 0; while (c = *cp++) { if (!isdigit(c)) { if (allints) return -1; break; } c -= '0'; if (c >= base) complain("You must specify in base %d.", base); value = value * base + c; } return value; } /* Read a (possibly signed) integer from CP, and add it to CURVAL if a sign was present and CURVAL was non-negative to start with. */ int schr_to_int(cp, base, curval) register const char *cp; int base; int curval; { register int sign = 0, value; if ((value = curval) >= 0) { if (*cp == '-') { --sign; value = 1; cp++; } else if (*cp == '+') { ++sign; value = 1; cp++; } } if (*cp == '\0' || (value = chr_to_int(cp, base, YES)) >= 0) { if (sign) { if (sign < 0) value = -value; if ((value += curval) < 0) value = 0; } } return value; } /* * [TRH] ask integer; allow offset to current value */ int ask_int(prompt, base, curval) const char *prompt; int base; int curval; { register char *valstr = NULL; register int value; if ((value = curval) >= 0) valstr = itos(value); if ((value = schr_to_int(ask(valstr, prompt), base, value)) < 0) complain("That's not a number!"); return value; } /* * [TRH] Ask integer, using exp (if valid) as default. */ int exp_ask_int(curval) { register int value; if (exp_p == NO) return ask_int(ProcFmt, 10, curval); if ((value = exp) < 0) if ((value += curval) < 0) value = 0; return value; } /*====================================================================== * $Log: misc.c,v $ * Revision 14.31.0.9 1994/02/01 20:35:18 tom * (EditParam): const-ify tmpfile. * * Revision 14.31 1993/02/17 00:42:33 tom * enable bidirectional "transpose-characters" for TINY; * remove (void) casts; lotsa random optimizations. * * Revision 14.30 1993/02/09 02:45:43 tom * cleanup whitespace; some random optimizations; add octal mode to "digit"; * be more careful of truncating filenames in Editparam(). * * Revision 14.28 1992/09/23 22:37:15 tom * make "help" transparent to kemap completion. * * Revision 14.27 1992/09/21 13:16:00 tom * replace CTL('Q') with `QuoteChar'. * * Revision 14.26 1992/08/26 23:56:56 tom * add RCS directives. * */