1: /*************************************************************************** 2: * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne. JOVE * 3: * is provided to you without charge, and with no warranty. You may give * 4: * away copies of JOVE, including sources, provided that this notice is * 5: * included in all the files. * 6: ***************************************************************************/ 7: 8: #include "jove.h" 9: #include "termcap.h" 10: #include "ctype.h" 11: #include <signal.h> 12: 13: #ifdef MAC 14: # include "mac.h" 15: #else 16: # include <varargs.h> 17: # ifdef F_COMPLETION 18: # include <sys/stat.h> 19: # endif 20: #endif /* MAC */ 21: 22: #ifdef MAC 23: # undef private 24: # define private 25: #endif 26: 27: #ifdef LINT_ARGS 28: private Buffer * get_minibuf(void); 29: private char * real_ask(char *, int (*)(), char *, char *); 30: 31: private int 32: f_complete(int), 33: bad_extension(char *), 34: crush_bads(char **, int), 35: isdir(char *); 36: private void 37: fill_in(char **, int), 38: EVexpand(void); 39: #else 40: private Buffer * get_minibuf(); 41: private char * real_ask(); 42: 43: private int 44: f_complete(), 45: bad_extension(), 46: crush_bads(), 47: isdir(); 48: private void 49: fill_in(), 50: EVexpand(); 51: #endif /* LINT_ARGS */ 52: 53: #ifdef MAC 54: # undef private 55: # define private static 56: #endif 57: 58: int AbortChar = CTL('G'), 59: DoEVexpand = NO; /* should we expand evironment variables? */ 60: 61: int Asking = NO; 62: char Minibuf[LBSIZE]; 63: private Line *CurAskPtr = 0; /* points at some line in mini-buffer */ 64: private Buffer *AskBuffer = 0; /* Askbuffer points to actual structure */ 65: 66: /* The way the mini-buffer works is this: The first line of the mini-buffer 67: is where the user does his stuff. The rest of the buffer contains 68: strings that the user often wants to use, for instance, file names, or 69: common search strings, etc. If he types C-N or C-P while in ask(), we 70: bump the point up or down a line and extract the contents (we make sure 71: is somewhere in the mini-buffer). */ 72: 73: static Buffer * 74: get_minibuf() 75: { 76: if (AskBuffer) { /* make sure ut still exists */ 77: register Buffer *b; 78: 79: for (b = world; b != 0; b = b->b_next) 80: if (b == AskBuffer) 81: return b; 82: } 83: AskBuffer = do_select((Window *) 0, "*minibuf*"); 84: AskBuffer->b_type = B_SCRATCH; 85: return AskBuffer; 86: } 87: 88: /* Add a string to the mini-buffer. */ 89: 90: void 91: minib_add(str, movedown) 92: char *str; 93: { 94: register Buffer *saveb = curbuf; 95: 96: SetBuf(get_minibuf()); 97: LineInsert(1); 98: ins_str(str, NO); 99: if (movedown) 100: CurAskPtr = curline; 101: SetBuf(saveb); 102: } 103: 104: /* look for any substrings of the form $foo in linebuf, and expand 105: them according to their value in the environment (if possible) - 106: this munges all over curchar and linebuf without giving it a second 107: thought (I must be getting lazy in my old age) */ 108: private void 109: EVexpand() 110: { 111: register int c; 112: register char *lp = linebuf, 113: *ep; 114: char varname[128], 115: *vp, 116: *lp_start; 117: Mark *m = MakeMark(curline, curchar, M_FLOATER); 118: 119: while (c = *lp++) { 120: if (c != '$') 121: continue; 122: lp_start = lp - 1; /* the $ */ 123: vp = varname; 124: while (c = *lp++) { 125: if (!isword(c)) 126: break; 127: *vp++ = c; 128: } 129: *vp = '\0'; 130: /* if we find an env. variable with the right 131: name, we insert it in linebuf, and then delete 132: the variable name that we're replacing - and 133: then we continue in case there are others ... */ 134: if (ep = getenv(varname)) { 135: curchar = lp_start - linebuf; 136: ins_str(ep, NO); 137: del_char(FORWARD, strlen(varname) + 1); 138: lp = linebuf + curchar; 139: } 140: } 141: ToMark(m); 142: DelMark(m); 143: } 144: 145: private char * 146: real_ask(delim, d_proc, def, prompt) 147: char *delim, 148: *def, 149: *prompt; 150: int (*d_proc)(); 151: { 152: static int InAsk = 0; 153: jmp_buf savejmp; 154: int c, 155: prompt_len; 156: Buffer *saveb = curbuf; 157: int abort = 0, 158: no_typed = 0; 159: data_obj *push_cmd = LastCmd; 160: int o_a_v = arg_value(), 161: o_i_an_a = is_an_arg(); 162: #ifdef MAC 163: menus_off(); 164: #endif 165: 166: if (InAsk) 167: complain((char *) 0); 168: push_env(savejmp); 169: InAsk += 1; 170: SetBuf(get_minibuf()); 171: if (!inlist(AskBuffer->b_first, CurAskPtr)) 172: CurAskPtr = curline; 173: prompt_len = strlen(prompt); 174: ToFirst(); /* Beginning of buffer. */ 175: linebuf[0] = '\0'; 176: modify(); 177: makedirty(curline); 178: 179: if (setjmp(mainjmp)) 180: if (InJoverc) { /* this is a kludge */ 181: abort = YES; 182: goto cleanup; 183: } 184: 185: for (;;) { 186: clr_arg_value(); 187: last_cmd = this_cmd; 188: init_strokes(); 189: cont: s_mess("%s%s", prompt, linebuf); 190: Asking = curchar + prompt_len; 191: c = getch(); 192: if ((c == EOF) || index(delim, c)) { 193: if (DoEVexpand) 194: EVexpand(); 195: if (d_proc == (int(*)())0 || (*d_proc)(c) == 0) 196: goto cleanup; 197: } else if (c == AbortChar) { 198: message("[Aborted]"); 199: abort = YES; 200: goto cleanup; 201: } else switch (c) { 202: case CTL('N'): 203: case CTL('P'): 204: if (CurAskPtr != 0) { 205: int n = (c == CTL('P') ? -arg_value() : arg_value()); 206: CurAskPtr = next_line(CurAskPtr, n); 207: if (CurAskPtr == curbuf->b_first && CurAskPtr->l_next != 0) 208: CurAskPtr = CurAskPtr->l_next; 209: (void) ltobuf(CurAskPtr, linebuf); 210: modify(); 211: makedirty(curline); 212: Eol(); 213: this_cmd = 0; 214: } 215: break; 216: 217: case CTL('R'): 218: if (def) 219: ins_str(def, NO); 220: else 221: rbell(); 222: break; 223: 224: default: 225: dispatch(c); 226: break; 227: } 228: if (curbuf != AskBuffer) 229: SetBuf(AskBuffer); 230: if (curline != curbuf->b_first) { 231: CurAskPtr = curline; 232: curline = curbuf->b_first; /* with whatever is in linebuf */ 233: } 234: if (this_cmd == ARG_CMD) 235: goto cont; 236: } 237: cleanup: 238: pop_env(savejmp); 239: 240: LastCmd = push_cmd; 241: set_arg_value(o_a_v); 242: set_is_an_arg(o_i_an_a); 243: no_typed = (linebuf[0] == '\0'); 244: strcpy(Minibuf, linebuf); 245: SetBuf(saveb); 246: InAsk = Asking = Interactive = NO; 247: if (!abort) { 248: if (!charp()) { 249: Placur(ILI, 0); 250: flusho(); 251: } 252: if (no_typed) 253: return 0; 254: } else 255: complain(mesgbuf); 256: return Minibuf; 257: } 258: 259: /* VARARGS2 */ 260: 261: char * 262: ask(def, fmt, va_alist) 263: char *def, 264: *fmt; 265: va_dcl 266: { 267: char prompt[128]; 268: char *ans; 269: va_list ap; 270: 271: va_start(ap); 272: format(prompt, sizeof prompt, fmt, ap); 273: va_end(ap); 274: ans = real_ask("\r\n", (int (*)()) 0, def, prompt); 275: if (ans == 0) { /* Typed nothing. */ 276: if (def == 0) 277: complain("[No default]"); 278: return def; 279: } 280: return ans; 281: } 282: 283: /* VARARGS1 */ 284: 285: char * 286: do_ask(delim, d_proc, def, fmt, va_alist) 287: char *delim, 288: *def, 289: *fmt; 290: int (*d_proc)(); 291: va_dcl 292: { 293: char prompt[128]; 294: va_list ap; 295: 296: va_start(ap); 297: format(prompt, sizeof prompt, fmt, ap); 298: va_end(ap); 299: return real_ask(delim, d_proc, def, prompt); 300: } 301: 302: /* VARARGS1 */ 303: 304: int 305: yes_or_no_p(fmt, va_alist) 306: char *fmt; 307: va_dcl 308: { 309: char prompt[128]; 310: int c; 311: va_list ap; 312: 313: va_start(ap); 314: format(prompt, sizeof prompt, fmt, ap); 315: va_end(ap); 316: for (;;) { 317: message(prompt); 318: Asking = strlen(prompt); /* so redisplay works */ 319: c = getch(); 320: Asking = NO; 321: if (c == AbortChar) 322: complain("[Aborted]"); 323: switch (CharUpcase(c)) { 324: case 'Y': 325: return YES; 326: 327: case 'N': 328: return NO; 329: 330: default: 331: add_mess("[Type Y or N]"); 332: SitFor(10); 333: } 334: } 335: /* NOTREACHED */ 336: } 337: 338: #ifdef F_COMPLETION 339: static char *fc_filebase; 340: int DispBadFs = YES; /* display bad file names? */ 341: #ifndef MSDOS 342: char BadExtensions[128] = ".o"; 343: #else /* MSDOS */ 344: char BadExtensions[128] = ".obj .exe .com .bak .arc .lib .zoo"; 345: #endif /* MSDOS */ 346: 347: static 348: bad_extension(name) 349: char *name; 350: { 351: char *ip, 352: *bads = BadExtensions; 353: int namelen = strlen(name), 354: ext_len, 355: stop = 0; 356: 357: do { 358: if ((ip = index(bads, ' ')) == 0) { 359: ip = bads + strlen(bads); 360: stop = YES; 361: } 362: if ((ext_len = ip - bads) == 0) 363: continue; 364: if ((ext_len < namelen) && 365: (strncmp(&name[namelen - ext_len], bads, ext_len) == 0)) 366: return YES; 367: } while ((bads = ip + 1), !stop); 368: return NO; 369: } 370: 371: int 372: f_match(file) 373: char *file; 374: { 375: int len = strlen(fc_filebase); 376: 377: if (DispBadFs == NO) 378: if (bad_extension(file)) 379: return NO; 380: 381: return ((len == 0) || 382: #ifdef MSDOS 383: (casencmp(file, fc_filebase, strlen(fc_filebase)) == 0) 384: #else 385: (strncmp(file, fc_filebase, strlen(fc_filebase)) == 0) 386: #endif 387: ); 388: } 389: 390: static 391: isdir(name) 392: char *name; 393: { 394: struct stat stbuf; 395: char filebuf[FILESIZE]; 396: 397: PathParse(name, filebuf); 398: return ((stat(filebuf, &stbuf) != -1) && 399: (stbuf.st_mode & S_IFDIR) == S_IFDIR); 400: } 401: 402: private void 403: fill_in(dir_vec, n) 404: register char **dir_vec; 405: { 406: int minmatch = 0, 407: numfound = 0, 408: lastmatch = -1, 409: i, 410: the_same = TRUE, /* After filling in, are we the same 411: as when we were called? */ 412: is_ntdir; /* Is Newly Typed Directory name */ 413: char bads[128]; 414: 415: for (i = 0; i < n; i++) { 416: /* if it's no, then we have already filtered them out 417: in f_match() so there's no point in doing it again */ 418: if (DispBadFs == YES) { 419: if (bad_extension(dir_vec[i])) 420: continue; 421: } 422: if (numfound) 423: minmatch = min(minmatch, 424: numcomp(dir_vec[lastmatch], dir_vec[i])); 425: else 426: minmatch = strlen(dir_vec[i]); 427: lastmatch = i; 428: numfound += 1; 429: } 430: /* Ugh. Beware--this is hard to get right in a reasonable 431: manner. Please excuse this code--it's past my bedtime. */ 432: if (numfound == 0) { 433: rbell(); 434: return; 435: } 436: Eol(); 437: if (minmatch > strlen(fc_filebase)) { 438: the_same = FALSE; 439: null_ncpy(fc_filebase, dir_vec[lastmatch], minmatch); 440: Eol(); 441: makedirty(curline); 442: } 443: is_ntdir = ((numfound == 1) && 444: (curchar > 0) && 445: (linebuf[curchar - 1] != '/') && 446: (isdir(linebuf))); 447: if (the_same && !is_ntdir) { 448: add_mess((n == 1) ? " [Unique]" : " [Ambiguous]"); 449: SitFor(7); 450: } 451: if (is_ntdir) 452: insert_c('/', 1); 453: } 454: 455: extern int alphacomp(); 456: 457: /* called from do_ask() when one of "\r\n ?" is typed. Does the right 458: thing, depending on which. */ 459: 460: static 461: f_complete(c) 462: { 463: char dir[FILESIZE], 464: **dir_vec; 465: int nentries, 466: i; 467: 468: if (c == CR || c == LF) 469: return 0; /* tells ask to return now */ 470: #ifndef MSDOS /* kg */ 471: if ((fc_filebase = rindex(linebuf, '/')) != 0) { 472: #else /* MSDOS */ 473: fc_filebase = rindex(linebuf, '/'); 474: if (fc_filebase == (char *)0) 475: fc_filebase = rindex(linebuf, '\\'); 476: if (fc_filebase == (char *)0) 477: fc_filebase = rindex(linebuf, ':'); 478: if (fc_filebase != (char *)0) { 479: #endif /* MSDOS */ 480: char tmp[FILESIZE]; 481: 482: fc_filebase += 1; 483: null_ncpy(tmp, linebuf, (fc_filebase - linebuf)); 484: if (tmp[0] == '\0') 485: strcpy(tmp, "/"); 486: PathParse(tmp, dir); 487: } else { 488: fc_filebase = linebuf; 489: strcpy(dir, "."); 490: } 491: if ((nentries = scandir(dir, &dir_vec, f_match, alphacomp)) == -1) { 492: add_mess(" [Unknown directory: %s]", dir); 493: SitFor(7); 494: return 1; 495: } 496: if (nentries == 0) { 497: add_mess(" [No match]"); 498: SitFor(7); 499: } else if (c == ' ' || c == '\t') 500: fill_in(dir_vec, nentries); 501: else { 502: /* we're a '?' */ 503: int maxlen = 0, 504: ncols, 505: col, 506: lines, 507: linespercol; 508: 509: TOstart("Completion", FALSE); /* false means newline only on request */ 510: Typeout("(! means file will not be chosen unless typed explicitly)"); 511: Typeout((char *) 0); 512: Typeout("Possible completions (in %s):", dir); 513: Typeout((char *) 0); 514: 515: for (i = 0; i < nentries; i++) 516: maxlen = max(strlen(dir_vec[i]), maxlen); 517: maxlen += 4; /* pad each column with at least 4 spaces */ 518: ncols = (CO - 2) / maxlen; 519: linespercol = 1 + (nentries / ncols); 520: 521: for (lines = 0; lines < linespercol; lines++) { 522: for (col = 0; col < ncols; col++) { 523: int isbad, 524: which; 525: 526: which = (col * linespercol) + lines; 527: if (which >= nentries) 528: break; 529: if (DispBadFs == YES) 530: isbad = bad_extension(dir_vec[which]); 531: else 532: isbad = NO; 533: Typeout("%s%-*s", isbad ? "!" : NullStr, 534: maxlen - isbad, dir_vec[which]); 535: } 536: Typeout((char *) 0); 537: } 538: TOstop(); 539: } 540: freedir(&dir_vec, nentries); 541: return 1; 542: } 543: 544: #endif 545: 546: char * 547: ask_file(prmt, def, buf) 548: char *prmt, 549: *def, 550: *buf; 551: { 552: char *ans, 553: prompt[128], 554: *pretty_name = pr_name(def, YES); 555: if (prmt) 556: sprintf(prompt, prmt); 557: else 558: sprintf(prompt, ProcFmt); 559: #ifdef F_COMPLETION 560: ans = real_ask("\r\n \t?", f_complete, pretty_name, prompt); 561: if (ans == 0) 562: complain((char *)0); 563: #else 564: ans = ask(pretty_name, prompt); 565: #endif 566: PathParse(ans, buf); 567: 568: return buf; 569: }