/************************************************************************ * 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. * ************************************************************************/ /* Contains commands that deal with creating, selecting, killing and listing buffers, and buffer modes, and find-file, etc. */ #include "jove.h" RCS("$Id: buf.c,v 14.31.0.12 1994/06/23 02:54:50 tom Exp tom $") #include "ctype.h" #include "io.h" #include "process.h" /* for ErrFree and IPROCS stuff */ const char Mainbuf[] = "Main"; const char NoName[] = "Sans un nom!"; Buffer *world ZERO, /* First in the list */ *curbuf ZERO, *lastbuf ZERO; /* Last buffer we were in so we have a default buffer during a select buffer. */ #ifdef MENUS int Bufchange ZERO, Modechange ZERO; #endif /* These arrays are indexed by Major/Minor modes so order is important! (the trailing space in `Desc' field is for the convenience of ModeLine.) */ const char * const MajorName[] = { DEF_MAJ( "fundamental-mode", FUNDAMENTAL, "Fundamental " ), DEF_MAJ( "text-mode", TEXT, "Text " ), DEF_MAJ( "c-mode", CMODE, "C " ), #ifdef LISP DEF_MAJ( "lisp-mode", LISPMODE, "Lisp " ), _IF(def LISP) #endif #ifdef PERL DEF_MAJ( "perl-mode", PERLMODE, "Perl " ), _IF(def PERL) #endif #ifdef ABBREV "Global ", /* not a real Major Mode but read_abbrev() needs it. */ NULL /* sentinel for match() */ #endif }; const char * const MinorName[] = { DEF_MIN( "auto-indent-mode", Indent, "AI " ), DEF_MIN( "show-match-mode", ShowMatch, "Match " ), DEF_MIN( "auto-fill-mode", Fill, "Fill " ), DEF_MIN( "over-write-mode", OverWrite, "OvrWt " ), DEF_MIN( "word-abbrev-mode", Abbrev, "Abbrev " ), _IF(def ABBREV) DEF_MIN( "view-mode", View, "View " ) }; /* The next are used in "list-buffers". */ private const char MajorChr[NMAJORS] = { 'F', /* Fundamental */ 'T', /* Text */ 'C', /* C mode */ #ifdef LISP 'L', /* Lisp */ #endif #ifdef PERL 'P', /* Perl */ #endif }; private const char MinorChr[] = { 'i', /* auto Indent */ 'm', /* show Match */ 'f', /* Fill */ 'o', /* Overwrite */ 'a', /* Abbrev */ 'v' /* View */ }; private const char * const TypeNames[] = { 0, "Scratch", "File", "Process" #ifdef IPROCS , "I-Process" #endif }; private void setbname __(( Buffer *_(b), const char *_(bname) )); /* Toggle BIT in the current buffer's minor mode flags. If argument is supplied, a positive one always turns on the mode and zero (or negative) argument always turns it off. */ void TogMinor(bit) register int bit; { register Buffer *b = curbuf; if (exp_p) { if (exp <= 0) b->b_minor &= ~bit; else b->b_minor |= bit; } else b->b_minor ^= bit; updmodline(); #ifdef MENUS Modechange++; #endif } /* Creates a new buffer, links it at the end of the buffer chain, initializes it and returns it. [TRH] merged buf_alloc and mak_buf. */ private Buffer *mak_buf __(( void )); private Buffer * mak_buf() { register Buffer *b, **hp; /* append to end of buffer chain */ for (hp = &world; (*hp); hp = &(*hp)->b_next) ; (*hp) = b = (Buffer *) emalloc(sizeof(Buffer)); bzero(b, sizeof(Buffer)); /* in effect, calloc */ b->b_name = NoName; b->b_major = TEXT; SETBUFTYPE(b, B_FILE); /* File until proven otherwise */ #ifdef MENUS b->Type = BUFFER; /* kludge, but simplifies menu handlers */ Bufchange++; #endif initlist(b); return b; } #ifndef TINY DEF_CMD( "scratch-buffer", NonExisting, ARG(B_SCRATCH) ); _IF(def TINY) DEF_CMD( "file-buffer", NonExisting, ARG(B_FILE) ); _IF(def TINY) DEF_CMD( "scratch-buffer", ChBufType, ARG(B_SCRATCH) ); _IF(ndef TINY) DEF_CMD( "file-buffer", ChBufType, ARG(B_FILE) ) _IF(ndef TINY) { register Buffer *b = curbuf; SETBUFTYPE(b, ObjArg(LastCmd)); updmodline(); } #endif DEF_CMD( "rename-buffer", ReNamBuf, NO ) { register const char *new, *prompt = ProcFmt; while (buf_exists(new = ask((char *) 0, prompt, new))) prompt = "%s already exists; new name? "; setbname(curbuf, new); } const char * ask_buf(def) const Buffer *def; { const char *bnames[400]; /* should be enough in most cases */ { register const Buffer *b; register const char **bnamp = bnames, **endp = &bnamp[sizeof(bnames)/sizeof(bnames[0])-1]; if (b = world) do { *bnamp++ = b->b_name; if (bnamp == endp) break; } while (b = b->b_next); *bnamp = NULL; } { register const char *bname = NULL; register int com; register const char *prompt = ProcFmt; if (def) { bname = def->b_name; prompt = ProcDefFmt; } if ((com = complete(bnames, RET_STATE, prompt, bname)) < 0) { Minibuf[BNAMESIZE-5] = '\0'; /* silently truncate... */ if (com == ORIGINAL || com == AMBIGUOUS) return Minibuf; if (com == NULLSTRING && bname) return bname; complain((char *)0); } return bnames[com]; } } /* choose a convenient default file name, for curbuf. */ const char * def_bfile() { register Buffer *cb = curbuf; register const char *fname; if ((fname = cb->b_fname) == NULL && (cb->b_type == B_FILE && strcmp(cb->b_name, Mainbuf) != 0)) fname = cb->b_name; return fname; } /* choose a convenient alternate buffer, for a given buffer `b'. */ const Buffer * def_buf(b) register const Buffer *b; { register const Buffer *defb; if ((defb = lastbuf) == NULL || defb == b) { register const Buffer *mainb = buf_exists(Mainbuf); /*- lastbuf = NULL; /* invalidate. */ defb = b; /* in case it was NULL. */ do { if (((defb = defb->b_next) == NULL && /* to make sure loop terminates when `b' is unlinked from the buffer list, `mainb' is made NULL when we see it the first time. (note: `world == NULL' implies `mainb == NULL', so everything is under control.) */ (defb = world, mainb == NULL)) || (defb == b)) { /* all around. */ defb = mainb; break; } } while ((defb->b_type != B_FILE) || (defb == mainb && (mainb = NULL, TRUE))); } return defb; } DEF_CMD( "find-file", BufSelect, ARG(1) ); DEF_CMD( "pick-buffer", BufSelect, ARG(2) ); DEF_CMD( "select-buffer", BufSelect, ARG(0) ) { register Buffer *cb = curbuf; register Buffer *newb; register int lno = 0; if (exp_p == YES) lno = exp; /* exp can be destroyed by do_find */ if (LastCmd->Type & ARG(1)) { /* "find-file" */ char fname[FILESIZE]; newb = do_find(curwind, ask_file((char *) 0, def_bfile(), fname), NO); } else { /* "select-buffer" / "pick-buffer" */ #define bname ((char *) newb) /* alias! {Cheat, for TINY jove} */ newb = (Buffer *) ask_buf(def_buf(cb)); /* "pick" is like "select" but allows existing buffers only. This is especially nice for selection-by-number. */ if ((LastCmd->Type & ARG(2)) && !buf_exists(bname)) complain("[No such buffer]"); newb = do_select(curwind, bname); #undef bname /* unalias! */ } SetBuf(newb); if (lno) to_line(lno); if (cb != newb) SetABuf(cb); } private void defb_wind __(( const Buffer *_(b) )); private void defb_wind(b) register const Buffer *b; { register Window *w = curwind; register const Buffer *altb = def_buf(b); for (;;) { if (w->w_bufp == b) { if (altb) do_select(w, altb->b_name); else if (one_windp(w)) do_select(w, Mainbuf); else { w = w->w_next; del_wind(w->w_prev); continue; /* we already advanced w */ } } if ((w = w->w_next) == curwind) break; } if (curbuf == b) SetBuf(w->w_bufp); } Buffer * getNMbuf() { register Buffer *delbuf; if ((delbuf = buf_exists(ask_buf(curbuf))) == NULL) complain("[No such buffer]"); if (IsModified(delbuf)) confirm("%b modified, are you sure? ", delbuf); return delbuf; } DEF_CMD( "erase-buffer", BufErase, EDIT ) { register Buffer *delbuf; if (delbuf = getNMbuf()) { initlist(delbuf); delbuf->b_modified = NO; updmodline(); /* Kludge... */ } } void kill_buf(delbuf) register Buffer *delbuf; { #ifdef IPROCS pbuftiedp(delbuf); /* check for lingering processes */ #endif { register Buffer **hp; for (hp = &world; (*hp) != delbuf; hp = &(*hp)->b_next) if ((*hp) == NULL) complain("?buffer?"); (*hp) = delbuf->b_next; } lfreelist(delbuf->b_first); { register Mark *m; while (m = delbuf->b_marks) del_mark(m, delbuf); /* ah, this was missing since day one */ } if (perr_buf == delbuf) { ErrFree(); perr_buf = NULL; } if (delbuf == lastbuf) SetABuf(curbuf); /* even if NULL! */ if (curwind) { /* so we can use (and delete) buffers BEFORE windows are set up. This is used for logging errors in FKEYS setup. */ defb_wind(delbuf); } if (curbuf == delbuf) { /* only happens when curwind not yet setup */ curbuf = NULL; SetABuf(NULL); } { register const char *name; if ((name = delbuf->b_name) != NoName) free((void_*) name); if (name = delbuf->b_fname) free((void_*) name); } free((void_*) delbuf); #ifdef MENUS Bufchange++; #endif } /* offer to kill some buffers */ DEF_CMD( "kill-some-buffers", KillSome, NO ) { register Buffer *b, *next; if (b = world) do { next = b->b_next; if (yes_or_no_p(NIL, "Kill %b? ", b) == NO) continue; if (IsModified(b) && yes_or_no_p('N', "%b modified; should I save it? ", b)) { register Buffer *oldb = curbuf; SetBuf(b); SaveFile(); SetBuf(oldb); } kill_buf(b); } while (b = next); } DEF_CMD( "delete-buffer", BufKill, NO ) { register Buffer *b; if (b = getNMbuf()) kill_buf(b); } private char *modes __(( const Buffer *_(b) )); private char * modes(b) const Buffer *b; { register char *d = genbuf; /* to build temp. string */ register const char *s = MinorChr; register unsigned m = b->b_minor; *d++ = MajorChr[b->b_major]; do { if (m & 1) *d++ = *s; } while (s++, m >>= 1); *d = '\0'; return genbuf; } DEF_CMD( "list-buffers", BufList, NO ) { register const Buffer *b; register int n, /* To give each buffer a number */ buf_width = 11, /* For the * (variable length field) */ any_notread = NO; #ifndef TINY if (exp < 0 && !ModBufs(0)) { message("No modified buffers."); return; } #endif if (b = world) do { any_notread |= b->b_ntbf; if ((n = strlen(b->b_name)) > buf_width) buf_width = n; } while (b = b->b_next); TOstart("*Buffer list*", TRUE); /* true means auto-newline */ Typeout("(* means buffer needs saving)"); if (any_notread) Typeout("(+ means file hasn't been read yet)"); Typeout((char *) 0); /* NOTE this should correspond with layout of the format below. */ Typeout("NO Lines Type Mode %-*s File", buf_width, "Name"); Typeout("-- ----- ---- ---- %-*s ----", buf_width, "----"); n = 0; if (b = world) do { ++n; #ifndef TINY /* show scratch buffers only if full buffer list requested */ if (exp_p != YES && b->b_type == B_SCRATCH) continue; /* just show modified buffers if argument is negative */ if (exp < 0 && !IsModified(b)) continue; #endif Typeout("%-2d %-5d %-9s %-8s%c %-*b %-s", n, lineno(b->b_last), TypeNames[b->b_type], modes(b), IsModified(b) ? '*' : (b->b_ntbf) ? '+' : ' ', buf_width, b, filename(b)); if (TOabort) break; } while (b = b->b_next); TOstop(); } private void bufname __(( Buffer *_(b) )); private void bufname(b) Buffer *b; { char bnamebuf[BNAMESIZE]; register int try = 0; register const char *bname, *basnam; if ((bname = b->b_fname) == NULL) complain("[No file name]"); basnam = bname = basename(bname); while (buf_exists(bname)) sprintf((char *)(bname = bnamebuf), "%s'%d", basnam, ++try); setbname(b, bname); } void initlist(b) register Buffer *b; { lfreelist(b->b_first); b->b_first = b->b_dot = b->b_last = NULL; listput(b, NULL); SavLine(b->b_dot, NullStr); b->b_char = 0; { register Mark *m; if (m = b->b_marks) do { MarkSet(m, b->b_dot, b->b_char); } while (m = m->m_next); } if (b == curbuf) /*getDOT(); -- since this is an empty line, boils down to: */ linebuf[0] = '\0'; if (perr_buf == b) { ErrFree(); perr_buf = NULL; } } /* Returns pointer to buffer with name NAME, or if NAME is a string of digits returns the buffer whose number equals those digits. Otherwise, returns 0. */ Buffer * buf_exists(name) const char *name; { register Buffer *bp; register int n; register const char *bname; if ((bname = name) == NULL) return NULL; if (bp = world) do { if (strcmp(bp->b_name, bname) == 0) return bp; } while (bp = bp->b_next); /* Doesn't match any names. Try for a buffer number... */ if ((n = schr_to_int(bname, 10, bufno(curbuf))) > 0) { if (bp = world) do { if (--n == 0) break; } while (bp = bp->b_next); } return bp; } /* Returns buffer pointer with a file name NAME, if one exists. Stat's the file and compares inodes, in case NAME is a link, as well as the actual characters that make up the file name. */ Buffer * file_exists(name) const char *name; { register Buffer *b = NULL; register const char *fname; if (fname = name) { char fnamebuf[FILESIZE]; struct stat filestat, linkstat; register int stat_result; fname = PathParse(name, fnamebuf); stat_result = zstat(fname, &filestat); if (b = world) do { if (b->b_fname == NULL) continue; if (strcmp(b->b_fname, fname) == 0 || /* Is it a link? */ (b_flink_p(b, stat_result, &filestat) #ifndef TINY /* probably; check that cached inode is still valid. {{this check is to avoid erroneously reporting a file that is already buffered in JOVE, then deleted from disk, and its inode reused for a new file, as a link to that file when that new file is visited.}} */ && b_flink_p(b, zstat(b->b_fname, &linkstat), &linkstat) #endif )) { /* If the file stored on disk has been changed from under our ass, re-visit it. */ if (b_foutdated_p(b, &filestat)) { register Buffer *oldb = curbuf; SetBuf(b); visit_file(fname, 0); SetBuf(oldb); } break; } } while (b = b->b_next); } return b; } private void setbname(b, name) register Buffer *b; const char *name; { register const char *bname; updmodline(); /* Kludge ... but speeds things up considerably */ if ((bname = b->b_name) != NoName) { free((void_*) bname); b->b_name = NoName; } if (bname = name) b->b_name = copystr(bname); #ifdef MENUS Bufchange++; #endif } void setfname(b, name) register Buffer *b; const char *name; { char wholename[FILESIZE]; register const char *oldname, *newname; Buffer *save = curbuf; SetBuf(b); /* BEFORE file name is set up! */ updmodline(); /* Kludge ... but speeds things up considerably */ oldname = b->b_fname; if (newname = name) { newname = copystr(PathParse(newname, wholename)); } b->b_fname = newname; DoAutoExec(newname, oldname); ino_val(b->b_ino, 0); /* until they're known. */ b->b_dev = 0; b->b_mtime = 0; b->b_statsize = 0; if (oldname) free((void_*) oldname); SetBuf(save); #ifdef MENUS Bufchange++; #endif } void set_ino(b) register Buffer *b; { struct stat stbuf; zstat(b->b_fname, &stbuf); ino_cpy(b->b_ino, stbuf.st_ino); b->b_dev = stbuf.st_dev; b->b_mtime = stbuf.st_mtime; b->b_statsize = stbuf.st_size; } /* Find the file `fname' into buf and put in in window `w' */ /* if `force' non-zero, force load the file and move dot to that line. */ Buffer * do_find(wind, fname, force) Window *wind; const char *fname; { register Buffer *b; register Window *w; if ((b = file_exists(fname)) == NULL) { b = mak_buf(); setfname(b, fname); bufname(b); set_ino(b); b->b_ntbf = 1; } if (force) { register Buffer *oldb = curbuf; SetBuf(b); /* this'll read the file */ to_line(force); SetBuf(oldb); } if (w = wind) tiewind(w, b); return b; } void SetBuf(buf) Buffer *buf; { register Buffer *newbuf; register Line *line; #ifdef FIXED_MAPS # ifdef IPROCS register Buffer *oldbuf = curbuf; # endif #endif if ((newbuf = buf) == NULL || newbuf == curbuf) return; lsave(); /* with old buffer, in case of OverWrite mode */ curbuf = newbuf; line = newbuf->b_dot; newbuf->b_dot = NULL; /* force DotTo to load linebuf (Urp!) */ DotTo(line, newbuf->b_char); /* Do the read now ... */ if (newbuf->b_ntbf) read_file(newbuf->b_fname, 0); SET_TABLE(newbuf->b_major); /* [TRH] set syntax table */ #ifdef FIXED_MAPS # ifdef IPROCS if (oldbuf && oldbuf->b_process) { if (!newbuf->b_process) /* exit i-process */ PopPBs(); } else { if (newbuf->b_process) /* enter i-process */ PushPBs(); } # endif #endif #ifdef MENUS Modechange++; #endif } Buffer * do_select(wind, name) Window *wind; register const char *name; { register Buffer *new; register Window *w; if ((new = buf_exists(name)) == NULL) { new = mak_buf(); /*- setfname(new, (char *) 0); -*/ setbname(new, name); } if (w = wind) tiewind(w, new); return new; } int bufno(buf) register Buffer *buf; { register Buffer *b; register int n = 0; if (b = world) do { ++n; if (b == buf) return n; } while (b = b->b_next); return 0; } /*====================================================================== * $Log: buf.c,v $ * Revision 14.31.0.12 1994/06/23 02:54:50 tom * (BufList): fix name of scratch buffer; (bufname): fix uniquized bufname. * * Revision 14.31.0.8 1993/11/11 16:07:43 tom * (SetBuf): call DotTo() to load linebuf and set point; this fixes bug in * OverWrite mode, which caused lossage if point was `beyond' end-of-line. * * Revision 14.31 1993/02/15 02:16:10 tom * remove (void) casts; fix visit_file bug in file_exists(); * lots of random optimizations. * * Revision 14.30 1993/02/06 00:48:31 tom * cleanup whitespace; some random optimizations; fix bug in def_buf(); * check for outdated file in file_exists() and offer to load the disk-based * version of the file; remove call to Active() in SetBuf()--active keymaps are * now determined whenever necessary by calls to active_map(). * * Revision 14.28 1992/10/24 01:24:18 tom * replace `pointer' with `void_*' convention from "portansi.h". * * Revision 14.27 1992/09/22 15:23:10 tom * file_exists(): improve link recognition. * * Revision 14.26 1992/08/26 23:56:50 tom * make setbname() private; inline AllMarkSet; add RCS directives. * */