/* Copyright (c) 1990,1991,1992 Chris and John Downey */ #ifndef lint static char *sccsid = "@(#)ex_cmds1.c 2.1 (Chris & John Downey) 7/29/92"; #endif /*** * program name: xvi * function: PD version of UNIX "vi" editor, with extensions. * module name: ex_cmds1.c * module function: File, window and buffer-related command functions for ex (colon) commands. * history: STEVIE - ST Editor for VI Enthusiasts, Version 3.10 Originally by Tim Thompson (twitch!tjt) Extensive modifications by Tony Andrews (onecom!wldrdg!tony) Heavily modified by Chris & John Downey ***/ #include "xvi.h" #ifdef MEGAMAX overlay "ex_cmds1" #endif static char **files; /* list of input files */ static int numfiles; /* number of input files */ static int curfile; /* number of the current file */ char *altfilename; static long altfileline; static char nowrtmsg[] = "No write since last change (use ! to override)"; static char nowrtbufs[] = "Some buffers not written (use ! to override)"; static bool_t more_files P((void)); void do_quit(window, force) Xviwin *window; bool_t force; { Xviwin *wp; bool_t changed; bool_t canexit; if (force) { canexit = TRUE; } else { /* * See if any buffers remain modified and unwritten. */ changed = FALSE; wp = window; do { if (is_modified(wp->w_buffer)) { changed = TRUE; } } while ((wp = next_window(wp)) != window); if (changed) { show_error(window, nowrtbufs); canexit = FALSE; } else { canexit = ! more_files(); } } if (canexit) { sys_exit(0); } } /* * Split the current window into two, leaving both windows mapped * onto the same buffer. */ void do_split_window(window) Xviwin *window; { Xviwin *newwin; newwin = split_window(window); if (newwin == NULL) { show_error(window, "No more windows!"); return; } map_window_onto_buffer(newwin, window->w_buffer); /* * Update the status line of the old window * (since it will have been moved). * Also update the window - this will almost certainly * have no effect on the screen, but is necessary. */ show_file_info(window); update_window(window); /* * Show the new window. */ init_sline(newwin); update_window(newwin); show_file_info(newwin); /* * Update the global window variable. */ curwin = newwin; } /* * Open a new buffer window, with a possible filename arg. * * do_buffer() is responsible for updating the screen image for the * old window, but not the new one, since we may want to move to a * different location in the new buffer (e.g. for a tag search). */ bool_t do_buffer(window, filename) Xviwin *window; char *filename; { Buffer *buffer; Buffer *new; Xviwin *newwin; buffer = window->w_buffer; if (window->w_nrows < (MINROWS + 1) * 2) { show_error(window, "Not enough room!"); return(FALSE); } new = new_buffer(); if (new == NULL) { show_error(window, "No more buffers!"); return(FALSE); } newwin = split_window(window); if (newwin == NULL) { free_buffer(new); show_error(window, "No more windows!"); return(FALSE); } map_window_onto_buffer(newwin, new); /* * Update the status lines of each buffer. * * Even if (echo & e_SHOWINFO) is turned off, show_file_info() * will always call update_sline(), which is what we really * need here. * * Note that we don't need to call move_window_to_cursor() for * the old window until it becomes the current window again. */ show_file_info(window); init_sline(newwin); if (filename != NULL) { (void) do_edit(newwin, FALSE, filename); } else { new->b_filename = new->b_tempfname = NULL; show_file_info(newwin); } update_window(window); /* * The current buffer (a global variable) has * to be updated here. No way around this. */ curbuf = new; curwin = newwin; return(TRUE); } /* * "close" (the current window). */ void do_close_window(win, force) Xviwin *win; bool_t force; { Buffer *buffer; Xviwin *best; buffer = win->w_buffer; if (is_modified(buffer) && !force && buffer->b_nwindows < 2) { /* * Don't close a modified buffer. */ show_error(win, nowrtmsg); } else if (next_window(win) != win || !more_files()) { Xviwin *w; /* * We can close this window if: * * ( * the buffer has not been modified * or they are forcing the close * or there are other windows onto this buffer * ) * AND * ( * there are other windows still open * or there are no more files to be edited * ) */ /* * Find an adjacent window to take up the screen * space used by the one being closed. */ best = NULL; for (w = next_window(win); w != win; w = next_window(w)) { if (w->w_cmdline + 1 == win->w_winpos || w->w_winpos - 1 == win->w_cmdline || w->w_nrows == 0) { /* * We have found an adjacent window; * if it is the first such, or if * it is smaller than the previous * best, it is now the new best. */ if (best == NULL || w->w_nrows < best->w_nrows) { best = w; } } } if (best == NULL) { sys_exit(0); } if (buffer->b_nwindows == 1 && buffer->b_filename != NULL) { /* * Before we free the buffer, save its filename. */ if (altfilename != NULL) { free(altfilename); } altfilename = buffer->b_filename; buffer->b_filename = NULL; altfileline = lineno(buffer, win->w_cursor->p_line); } /* * Now "best" points to the smallest adjacent window; * amalgamate the spaces used. */ if (best->w_winpos > win->w_winpos) { best->w_winpos = win->w_winpos; } best->w_nrows += win->w_nrows; best->w_cmdline = best->w_winpos + best->w_nrows - 1; free_window(win); if (buffer->b_nwindows == 0) { free_buffer(buffer); } /* * Have to update the globals "curbuf" and "curwin" here. */ curwin = best; curbuf = best->w_buffer; { unsigned savecho; savecho = echo; /* * Adjust position of new current window * within buffer before updating it, to avoid * wasting screen output - but don't do any * scrolling at this stage because the old * window is still on the screen. */ echo &= ~(e_CHARUPDATE | e_SHOWINFO | e_SCROLL); move_window_to_cursor(curwin); echo = savecho; } update_window(curwin); show_file_info(curwin); } } /* * Close current window. * * If it is the last window onto the buffer, also close the buffer. * * If the buffer has been modified, we must write it out before closing it. */ void do_xit(window) Xviwin *window; { Buffer *buffer; buffer = window->w_buffer; if (is_modified(buffer) && buffer->b_nwindows < 2) { if (buffer->b_filename != NULL) { if (!writeit(window, buffer->b_filename, (Line *) NULL, (Line *) NULL, FALSE)) { return; } } else { show_error(window, "No output file"); return; } } do_close_window(window, FALSE); } /* * Edit the given filename in the given buffer, * replacing any current contents. Note that the * screen is not updated, since there are routines * which use this function before moving the cursor * to a different position in the file. * * Returns TRUE for success, FALSE for failure. */ bool_t do_edit(window, force, arg) Xviwin *window; bool_t force; char *arg; { long line = 1; /* line # to go to in new file */ long nlines; /* no of lines read from file */ Line *head; /* start of list of lines */ Line *tail; /* last element of list of lines */ bool_t readonly; /* true if cannot write file */ Buffer *buffer; Xviwin *wp; buffer = window->w_buffer; if (!force && is_modified(buffer)) { show_error(window, nowrtmsg); return(FALSE); } if (arg == NULL || arg[0] == '\0') { /* * No filename specified; we must already have one. */ if (buffer->b_filename == NULL) { show_error(window, "No filename"); return(FALSE); } } else /* arg != NULL */ { /* * Filename specified. */ /* * First detect a ":e" on the current file. This is mainly * for ":ta" commands where the destination is within the * current file. */ if (buffer->b_filename != NULL && strcmp(arg, buffer->b_filename) == 0) { if (!is_modified(buffer) || (is_modified(buffer) && !force)) { return(TRUE); } } /* * Detect an edit of the alternate file, and set * the line number. */ if (altfilename != NULL && strcmp(arg, altfilename) == 0) { line = altfileline; } /* * Save the name of the previous file. * If the strsave() of the new filename * fails, we will have lost the previous * value of altfilename. What a shame. */ if (buffer->b_filename != NULL) { if (altfilename != NULL) free(altfilename); altfilename = strsave(buffer->b_filename); altfileline = lineno(buffer, window->w_cursor->p_line); } /* * Edit a named file. */ buffer->b_filename = strsave(arg); if (buffer->b_filename == NULL) return(FALSE); if (buffer->b_tempfname != NULL) free(buffer->b_tempfname); buffer->b_tempfname = NULL; } /* * Clear out the old buffer and read the file. */ if (clear_buffer(buffer) == FALSE) { show_error(window, "Out of memory"); return(FALSE); } /* * Be sure to re-map all window structures onto the buffer, * in order to eliminate any pointers into the old buffer. */ wp = window; do { if (wp->w_buffer != buffer) continue; unmap_window(wp); map_window_onto_buffer(wp, buffer); } while ((wp = next_window(wp)) != window); readonly = Pb(P_readonly) || !can_write(buffer->b_filename); nlines = get_file(window, buffer->b_filename, &head, &tail, (readonly ? " [Read only]" : ""), " [New file]"); update_sline(window); /* ensure colour is updated */ if (nlines == gf_NEWFILE) { /* no such file */ return(FALSE); } else if (nlines >= 0) { unsigned savecho; /* * Success. */ if (readonly) { buffer->b_flags |= FL_READONLY; } else { buffer->b_flags &= ~FL_READONLY; } if (nlines == 0) { /* empty file */ return(TRUE); } /* * We have successfully read the file in, * so now we must link it into the buffer. */ replbuffer(window, head); move_cursor(window, gotoline(buffer, line), 0); begin_line(window, TRUE); setpcmark(window); /* * We only call update_window() here because we want * window->w_botline to be updated; we don't let it do any * actual screen updating, for the reason explained above. */ savecho = echo; echo &= ~(e_CHARUPDATE | e_SCROLL | e_REPORT | e_SHOWINFO); update_window(window); echo = savecho; return(TRUE); } else { /* * We failed to read in the file. An appropriate * message will already have been printed by * get_file() (or alloc()). */ if (buffer->b_filename != NULL) free(buffer->b_filename); if (buffer->b_tempfname != NULL) free(buffer->b_tempfname); buffer->b_filename = buffer->b_tempfname = NULL; return(FALSE); } } void do_args(window) Xviwin *window; { register char *tmpbuf; int count; register int curpos = 0; if (numfiles == 0) { show_message(window, "No files"); return; } tmpbuf = alloc((unsigned) window->w_ncols + 1); if (tmpbuf == NULL) { return; } for (count = 0; count < numfiles; count++) { register char *sp; if (count == curfile && curpos < window->w_ncols) tmpbuf[curpos++] = '['; for (sp = files[count]; curpos < window->w_ncols && (tmpbuf[curpos] = *sp++) != '\0'; curpos++) { ; } if (count == curfile && curpos < window->w_ncols) tmpbuf[curpos++] = ']'; if (curpos < window->w_ncols) tmpbuf[curpos++] = ' '; } tmpbuf[curpos < window->w_ncols ? curpos : window->w_ncols] = '\0'; show_message(window, "%s", tmpbuf); free(tmpbuf); } /* * Change the current file list to the one specified, or edit the next * file in the current file list, or edit the next file in the list if * no argument is given. */ void do_next(window, argc, argv, force) Xviwin *window; int argc; char *argv[]; bool_t force; { unsigned savecho; savecho = echo; if (argc > 0) { int count; /* * Arguments given - this means a new set of filenames. */ if (!force && is_modified(window->w_buffer)) { show_error(window, nowrtmsg); return; } /* * There were no files before, so start from square one. */ if (numfiles == 0) { files = (char **) alloc((unsigned) argc * sizeof(char *)); if (files == NULL) { return; } } else { /* * We can change the existing list of files. * Free up all the individual filenames * which we got last time. */ for (count = 0; count < numfiles; count++) { free(files[count]); } if (argc != numfiles) { files = (char **) realloc((char *) files, (unsigned) argc * sizeof(char *)); if (files == NULL) { numfiles = 0; return; } } } /* * Now record all the new filenames. */ for (count = 0; count < argc; count++) { files[count] = strsave(argv[count]); if (files[count] == NULL) { /* * Aargh. Failed half-way through. * Clean up the mess ... */ while (--count >= 0) free(files[count]); free((char *) files); files = NULL; numfiles = 0; return; } } numfiles = argc; curfile = 0; /* * And try to edit the first few of them. * * In this case, we don't want report() or * show_file_info() to be called, because otherwise * the messages printed by get_file() won't be seen. */ echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO); (void) do_edit(curwin, force, files[0]); /* * This is not very good because it * doesn't split the screen evenly for * autosplit > 2. However, it will * just have to do for the moment. */ /* * Update the current window before * creating any new ones. */ move_window_to_cursor(curwin); while ((curfile + 1) < numfiles && can_split()) { bool_t success; success = do_buffer(curwin, files[++curfile]); /* * Make sure move_window_to_cursor() is called * for every window before calling * update_buffer(). */ move_window_to_cursor(curwin); if (!success) break; } update_window(curwin); } else if ((curfile + 1) < numfiles) { /* * No arguments; this is the normal usage, and * indicates we should edit the next file in the list. * Don't grab the next file if the current one is * modified and not written, or we will "lose" * files from the list. */ if (!force && is_modified(window->w_buffer)) { show_error(window, nowrtmsg); return; } /* * Just edit the next file. */ echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO); (void) do_edit(window, force, files[++curfile]); move_window_to_cursor(window); update_buffer(window->w_buffer); } else { show_message(window, "No more files"); } echo = savecho; } /*ARGSUSED*/ void do_rewind(window, force) Xviwin *window; bool_t force; { unsigned savecho; if (numfiles <= 1) /* nothing to rewind */ return; curfile = 0; savecho = echo; echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO); (void) do_edit(window, force, files[0]); move_window_to_cursor(window); update_buffer(window->w_buffer); echo = savecho; } /* * Write out the buffer, to the given filename, * from "line1" to "line2", forcing if necessary. * * If no filename given, use the buffer's filename. */ bool_t do_write(window, filename, l1, l2, force) Xviwin *window; char *filename; Line *l1, *l2; bool_t force; { if (filename == NULL) { filename = window->w_buffer->b_filename; } if (filename == NULL) { show_error(window, "No output file"); return(FALSE); } else { return(writeit(window, filename, l1, l2, force)); } } /* * Write to the given filename then quit. */ void do_wq(window, filename, force) Xviwin *window; char *filename; bool_t force; { if (do_write(window, filename, (Line *) NULL, (Line *) NULL, force)) { do_quit(window, force); } } /* * Read the given file into the buffer after the specified line. * The line may not be NULL, but should be a line in the buffer * referenced by the passed window parameter. */ void do_read(window, filename, atline) Xviwin *window; char *filename; Line *atline; { Line *head; /* start of list of lines */ Line *tail; /* last element of list of lines */ long nlines; /* number of lines read */ nlines = get_file(window, filename, &head, &tail, "", " No such file"); /* * If nlines > 0, we need to insert the lines returned into * the buffer. Otherwise, either the file is empty or an error * message has already been printed: in either case, we don't * need to do anything. */ if (nlines > 0) { /* * We want to see the message printed by * get_file() here, not the message printed by * report(). */ echo &= ~e_REPORT; repllines(window, atline->l_next, 0L, head); echo |= e_REPORT; update_buffer(window->w_buffer); /* * Move the cursor to the first character * of the file we just read in. */ move_cursor(window, atline->l_next, 0); begin_line(window, TRUE); } } /* * Edit alternate file. Called when control-^ is typed. */ void do_alt_edit(window) Xviwin *window; { if (altfilename == NULL) { show_error(window, "No alternate file to edit"); } else { if (do_buffer(window, altfilename)) { move_window_to_cursor(curwin); update_window(curwin); } } } void do_compare() { Xviwin *w; enum mvtype incres; Posn pos1, pos2; w = next_window(curwin); if (w == curwin) { show_error(curwin, "No other buffers to compare"); } else if (w->w_buffer == curbuf) { show_error(curwin, "Next window has same buffer"); } else { pos1 = *(curwin->w_cursor); pos2 = *(w->w_cursor); while ((incres = inc(&pos1)) == inc(&pos2)) { if (incres == mv_EOL) { continue; } else if (incres == mv_NOMOVE) { (void) dec(&pos1); (void) dec(&pos2); break; } else { if (gchar(&pos1) != gchar(&pos2)) { break; } } } if (gchar(&pos1) == '\0' && pos1.p_index > 0) { (void) dec(&pos1); } if (gchar(&pos2) == '\0' && pos2.p_index > 0) { (void) dec(&pos2); } move_cursor(curwin, pos1.p_line, pos1.p_index); move_cursor(w, pos2.p_line, pos2.p_index); move_window_to_cursor(w); cursupdate(w); wind_goto(w); } } static bool_t more_files() { int n; n = numfiles - (curfile + 1); if (n > 0) { show_error(curwin, "%d more file%s to edit", n, (n > 1) ? "s" : ""); return(TRUE); } else { return(FALSE); } }