]> git.cworth.org Git - apitrace/blobdiff - thirdparty/less/edit.c
Bundle less (version 444) for Windows.
[apitrace] / thirdparty / less / edit.c
diff --git a/thirdparty/less/edit.c b/thirdparty/less/edit.c
new file mode 100644 (file)
index 0000000..4781d95
--- /dev/null
@@ -0,0 +1,818 @@
+/*
+ * Copyright (C) 1984-2011  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information about less, or for information on how to 
+ * contact the author, see the README file.
+ */
+
+
+#include "less.h"
+#if HAVE_STAT
+#include <sys/stat.h>
+#endif
+
+public int fd0 = 0;
+
+extern int new_file;
+extern int errmsgs;
+extern int cbufs;
+extern char *every_first_cmd;
+extern int any_display;
+extern int force_open;
+extern int is_tty;
+extern int sigs;
+extern IFILE curr_ifile;
+extern IFILE old_ifile;
+extern struct scrpos initial_scrpos;
+extern void constant *ml_examine;
+#if SPACES_IN_FILENAMES
+extern char openquote;
+extern char closequote;
+#endif
+
+#if LOGFILE
+extern int logfile;
+extern int force_logfile;
+extern char *namelogfile;
+#endif
+
+#if HAVE_STAT_INO
+public dev_t curr_dev;
+public ino_t curr_ino;
+#endif
+
+char *curr_altfilename = NULL;
+static void *curr_altpipe;
+
+
+/*
+ * Textlist functions deal with a list of words separated by spaces.
+ * init_textlist sets up a textlist structure.
+ * forw_textlist uses that structure to iterate thru the list of
+ * words, returning each one as a standard null-terminated string.
+ * back_textlist does the same, but runs thru the list backwards.
+ */
+       public void
+init_textlist(tlist, str)
+       struct textlist *tlist;
+       char *str;
+{
+       char *s;
+#if SPACES_IN_FILENAMES
+       int meta_quoted = 0;
+       int delim_quoted = 0;
+       char *esc = get_meta_escape();
+       int esclen = strlen(esc);
+#endif
+       
+       tlist->string = skipsp(str);
+       tlist->endstring = tlist->string + strlen(tlist->string);
+       for (s = str;  s < tlist->endstring;  s++)
+       {
+#if SPACES_IN_FILENAMES
+               if (meta_quoted)
+               {
+                       meta_quoted = 0;
+               } else if (esclen > 0 && s + esclen < tlist->endstring &&
+                          strncmp(s, esc, esclen) == 0)
+               {
+                       meta_quoted = 1;
+                       s += esclen - 1;
+               } else if (delim_quoted)
+               {
+                       if (*s == closequote)
+                               delim_quoted = 0;
+               } else /* (!delim_quoted) */
+               {
+                       if (*s == openquote)
+                               delim_quoted = 1;
+                       else if (*s == ' ')
+                               *s = '\0';
+               }
+#else
+               if (*s == ' ')
+                       *s = '\0';
+#endif
+       }
+}
+
+       public char *
+forw_textlist(tlist, prev)
+       struct textlist *tlist;
+       char *prev;
+{
+       char *s;
+       
+       /*
+        * prev == NULL means return the first word in the list.
+        * Otherwise, return the word after "prev".
+        */
+       if (prev == NULL)
+               s = tlist->string;
+       else
+               s = prev + strlen(prev);
+       if (s >= tlist->endstring)
+               return (NULL);
+       while (*s == '\0')
+               s++;
+       if (s >= tlist->endstring)
+               return (NULL);
+       return (s);
+}
+
+       public char *
+back_textlist(tlist, prev)
+       struct textlist *tlist;
+       char *prev;
+{
+       char *s;
+       
+       /*
+        * prev == NULL means return the last word in the list.
+        * Otherwise, return the word before "prev".
+        */
+       if (prev == NULL)
+               s = tlist->endstring;
+       else if (prev <= tlist->string)
+               return (NULL);
+       else
+               s = prev - 1;
+       while (*s == '\0')
+               s--;
+       if (s <= tlist->string)
+               return (NULL);
+       while (s[-1] != '\0' && s > tlist->string)
+               s--;
+       return (s);
+}
+
+/*
+ * Close the current input file.
+ */
+       static void
+close_file()
+{
+       struct scrpos scrpos;
+       
+       if (curr_ifile == NULL_IFILE)
+               return;
+
+       /*
+        * Save the current position so that we can return to
+        * the same position if we edit this file again.
+        */
+       get_scrpos(&scrpos);
+       if (scrpos.pos != NULL_POSITION)
+       {
+               store_pos(curr_ifile, &scrpos);
+               lastmark();
+       }
+       /*
+        * Close the file descriptor, unless it is a pipe.
+        */
+       ch_close();
+       /*
+        * If we opened a file using an alternate name,
+        * do special stuff to close it.
+        */
+       if (curr_altfilename != NULL)
+       {
+               close_altfile(curr_altfilename, get_filename(curr_ifile),
+                               curr_altpipe);
+               free(curr_altfilename);
+               curr_altfilename = NULL;
+       }
+       curr_ifile = NULL_IFILE;
+#if HAVE_STAT_INO
+       curr_ino = curr_dev = 0;
+#endif
+}
+
+/*
+ * Edit a new file (given its name).
+ * Filename == "-" means standard input.
+ * Filename == NULL means just close the current file.
+ */
+       public int
+edit(filename)
+       char *filename;
+{
+       if (filename == NULL)
+               return (edit_ifile(NULL_IFILE));
+       return (edit_ifile(get_ifile(filename, curr_ifile)));
+}
+       
+/*
+ * Edit a new file (given its IFILE).
+ * ifile == NULL means just close the current file.
+ */
+       public int
+edit_ifile(ifile)
+       IFILE ifile;
+{
+       int f;
+       int answer;
+       int no_display;
+       int chflags;
+       char *filename;
+       char *open_filename;
+       char *qopen_filename;
+       char *alt_filename;
+       void *alt_pipe;
+       IFILE was_curr_ifile;
+       PARG parg;
+               
+       if (ifile == curr_ifile)
+       {
+               /*
+                * Already have the correct file open.
+                */
+               return (0);
+       }
+
+       /*
+        * We must close the currently open file now.
+        * This is necessary to make the open_altfile/close_altfile pairs
+        * nest properly (or rather to avoid nesting at all).
+        * {{ Some stupid implementations of popen() mess up if you do:
+        *    fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
+        */
+#if LOGFILE
+       end_logfile();
+#endif
+       was_curr_ifile = save_curr_ifile();
+       if (curr_ifile != NULL_IFILE)
+       {
+               chflags = ch_getflags();
+               close_file();
+               if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1)
+               {
+                       /*
+                        * Don't keep the help file in the ifile list.
+                        */
+                       del_ifile(was_curr_ifile);
+                       was_curr_ifile = old_ifile;
+               }
+       }
+
+       if (ifile == NULL_IFILE)
+       {
+               /*
+                * No new file to open.
+                * (Don't set old_ifile, because if you call edit_ifile(NULL),
+                *  you're supposed to have saved curr_ifile yourself,
+                *  and you'll restore it if necessary.)
+                */
+               unsave_ifile(was_curr_ifile);
+               return (0);
+       }
+
+       filename = save(get_filename(ifile));
+       /*
+        * See if LESSOPEN specifies an "alternate" file to open.
+        */
+       alt_pipe = NULL;
+       alt_filename = open_altfile(filename, &f, &alt_pipe);
+       open_filename = (alt_filename != NULL) ? alt_filename : filename;
+       qopen_filename = shell_unquote(open_filename);
+
+       chflags = 0;
+       if (alt_pipe != NULL)
+       {
+               /*
+                * The alternate "file" is actually a pipe.
+                * f has already been set to the file descriptor of the pipe
+                * in the call to open_altfile above.
+                * Keep the file descriptor open because it was opened 
+                * via popen(), and pclose() wants to close it.
+                */
+               chflags |= CH_POPENED;
+       } else if (strcmp(open_filename, "-") == 0)
+       {
+               /* 
+                * Use standard input.
+                * Keep the file descriptor open because we can't reopen it.
+                */
+               f = fd0;
+               chflags |= CH_KEEPOPEN;
+               /*
+                * Must switch stdin to BINARY mode.
+                */
+               SET_BINARY(f);
+#if MSDOS_COMPILER==DJGPPC
+               /*
+                * Setting stdin to binary by default causes
+                * Ctrl-C to not raise SIGINT.  We must undo
+                * that side-effect.
+                */
+               __djgpp_set_ctrl_c(1);
+#endif
+       } else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
+       {
+               f = -1;
+               chflags |= CH_HELPFILE;
+       } else if ((parg.p_string = bad_file(open_filename)) != NULL)
+       {
+               /*
+                * It looks like a bad file.  Don't try to open it.
+                */
+               error("%s", &parg);
+               free(parg.p_string);
+           err1:
+               if (alt_filename != NULL)
+               {
+                       close_altfile(alt_filename, filename, alt_pipe);
+                       free(alt_filename);
+               }
+               del_ifile(ifile);
+               free(qopen_filename);
+               free(filename);
+               /*
+                * Re-open the current file.
+                */
+               if (was_curr_ifile == ifile)
+               {
+                       /*
+                        * Whoops.  The "current" ifile is the one we just deleted.
+                        * Just give up.
+                        */
+                       quit(QUIT_ERROR);
+               }
+               reedit_ifile(was_curr_ifile);
+               return (1);
+       } else if ((f = open(qopen_filename, OPEN_READ)) < 0)
+       {
+               /*
+                * Got an error trying to open it.
+                */
+               parg.p_string = errno_message(filename);
+               error("%s", &parg);
+               free(parg.p_string);
+               goto err1;
+       } else 
+       {
+               chflags |= CH_CANSEEK;
+               if (!force_open && !opened(ifile) && bin_file(f))
+               {
+                       /*
+                        * Looks like a binary file.  
+                        * Ask user if we should proceed.
+                        */
+                       parg.p_string = filename;
+                       answer = query("\"%s\" may be a binary file.  See it anyway? ",
+                               &parg);
+                       if (answer != 'y' && answer != 'Y')
+                       {
+                               close(f);
+                               goto err1;
+                       }
+               }
+       }
+
+       /*
+        * Get the new ifile.
+        * Get the saved position for the file.
+        */
+       if (was_curr_ifile != NULL_IFILE)
+       {
+               old_ifile = was_curr_ifile;
+               unsave_ifile(was_curr_ifile);
+       }
+       curr_ifile = ifile;
+       curr_altfilename = alt_filename;
+       curr_altpipe = alt_pipe;
+       set_open(curr_ifile); /* File has been opened */
+       get_pos(curr_ifile, &initial_scrpos);
+       new_file = TRUE;
+       ch_init(f, chflags);
+
+       if (!(chflags & CH_HELPFILE))
+       {
+#if LOGFILE
+               if (namelogfile != NULL && is_tty)
+                       use_logfile(namelogfile);
+#endif
+#if HAVE_STAT_INO
+               /* Remember the i-number and device of the opened file. */
+               {
+                       struct stat statbuf;
+                       int r = stat(qopen_filename, &statbuf);
+                       if (r == 0)
+                       {
+                               curr_ino = statbuf.st_ino;
+                               curr_dev = statbuf.st_dev;
+                       }
+               }
+#endif
+               if (every_first_cmd != NULL)
+                       ungetsc(every_first_cmd);
+       }
+
+       free(qopen_filename);
+       no_display = !any_display;
+       flush();
+       any_display = TRUE;
+
+       if (is_tty)
+       {
+               /*
+                * Output is to a real tty.
+                */
+
+               /*
+                * Indicate there is nothing displayed yet.
+                */
+               pos_clear();
+               clr_linenum();
+#if HILITE_SEARCH
+               clr_hilite();
+#endif
+               cmd_addhist(ml_examine, filename);
+               if (no_display && errmsgs > 0)
+               {
+                       /*
+                        * We displayed some messages on error output
+                        * (file descriptor 2; see error() function).
+                        * Before erasing the screen contents,
+                        * display the file name and wait for a keystroke.
+                        */
+                       parg.p_string = filename;
+                       error("%s", &parg);
+               }
+       }
+       free(filename);
+       return (0);
+}
+
+/*
+ * Edit a space-separated list of files.
+ * For each filename in the list, enter it into the ifile list.
+ * Then edit the first one.
+ */
+       public int
+edit_list(filelist)
+       char *filelist;
+{
+       IFILE save_ifile;
+       char *good_filename;
+       char *filename;
+       char *gfilelist;
+       char *gfilename;
+       struct textlist tl_files;
+       struct textlist tl_gfiles;
+
+       save_ifile = save_curr_ifile();
+       good_filename = NULL;
+       
+       /*
+        * Run thru each filename in the list.
+        * Try to glob the filename.  
+        * If it doesn't expand, just try to open the filename.
+        * If it does expand, try to open each name in that list.
+        */
+       init_textlist(&tl_files, filelist);
+       filename = NULL;
+       while ((filename = forw_textlist(&tl_files, filename)) != NULL)
+       {
+               gfilelist = lglob(filename);
+               init_textlist(&tl_gfiles, gfilelist);
+               gfilename = NULL;
+               while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
+               {
+                       if (edit(gfilename) == 0 && good_filename == NULL)
+                               good_filename = get_filename(curr_ifile);
+               }
+               free(gfilelist);
+       }
+       /*
+        * Edit the first valid filename in the list.
+        */
+       if (good_filename == NULL)
+       {
+               unsave_ifile(save_ifile);
+               return (1);
+       }
+       if (get_ifile(good_filename, curr_ifile) == curr_ifile)
+       {
+               /*
+                * Trying to edit the current file; don't reopen it.
+                */
+               unsave_ifile(save_ifile);
+               return (0);
+       }
+       reedit_ifile(save_ifile);
+       return (edit(good_filename));
+}
+
+/*
+ * Edit the first file in the command line (ifile) list.
+ */
+       public int
+edit_first()
+{
+       curr_ifile = NULL_IFILE;
+       return (edit_next(1));
+}
+
+/*
+ * Edit the last file in the command line (ifile) list.
+ */
+       public int
+edit_last()
+{
+       curr_ifile = NULL_IFILE;
+       return (edit_prev(1));
+}
+
+
+/*
+ * Edit the n-th next or previous file in the command line (ifile) list.
+ */
+       static int
+edit_istep(h, n, dir)
+       IFILE h;
+       int n;
+       int dir;
+{
+       IFILE next;
+
+       /*
+        * Skip n filenames, then try to edit each filename.
+        */
+       for (;;)
+       {
+               next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
+               if (--n < 0)
+               {
+                       if (edit_ifile(h) == 0)
+                               break;
+               }
+               if (next == NULL_IFILE)
+               {
+                       /*
+                        * Reached end of the ifile list.
+                        */
+                       return (1);
+               }
+               if (ABORT_SIGS())
+               {
+                       /*
+                        * Interrupt breaks out, if we're in a long
+                        * list of files that can't be opened.
+                        */
+                       return (1);
+               }
+               h = next;
+       } 
+       /*
+        * Found a file that we can edit.
+        */
+       return (0);
+}
+
+       static int
+edit_inext(h, n)
+       IFILE h;
+       int n;
+{
+       return (edit_istep(h, n, +1));
+}
+
+       public int
+edit_next(n)
+       int n;
+{
+       return edit_istep(curr_ifile, n, +1);
+}
+
+       static int
+edit_iprev(h, n)
+       IFILE h;
+       int n;
+{
+       return (edit_istep(h, n, -1));
+}
+
+       public int
+edit_prev(n)
+       int n;
+{
+       return edit_istep(curr_ifile, n, -1);
+}
+
+/*
+ * Edit a specific file in the command line (ifile) list.
+ */
+       public int
+edit_index(n)
+       int n;
+{
+       IFILE h;
+
+       h = NULL_IFILE;
+       do
+       {
+               if ((h = next_ifile(h)) == NULL_IFILE)
+               {
+                       /*
+                        * Reached end of the list without finding it.
+                        */
+                       return (1);
+               }
+       } while (get_index(h) != n);
+
+       return (edit_ifile(h));
+}
+
+       public IFILE
+save_curr_ifile()
+{
+       if (curr_ifile != NULL_IFILE)
+               hold_ifile(curr_ifile, 1);
+       return (curr_ifile);
+}
+
+       public void
+unsave_ifile(save_ifile)
+       IFILE save_ifile;
+{
+       if (save_ifile != NULL_IFILE)
+               hold_ifile(save_ifile, -1);
+}
+
+/*
+ * Reedit the ifile which was previously open.
+ */
+       public void
+reedit_ifile(save_ifile)
+       IFILE save_ifile;
+{
+       IFILE next;
+       IFILE prev;
+
+       /*
+        * Try to reopen the ifile.
+        * Note that opening it may fail (maybe the file was removed),
+        * in which case the ifile will be deleted from the list.
+        * So save the next and prev ifiles first.
+        */
+       unsave_ifile(save_ifile);
+       next = next_ifile(save_ifile);
+       prev = prev_ifile(save_ifile);
+       if (edit_ifile(save_ifile) == 0)
+               return;
+       /*
+        * If can't reopen it, open the next input file in the list.
+        */
+       if (next != NULL_IFILE && edit_inext(next, 0) == 0)
+               return;
+       /*
+        * If can't open THAT one, open the previous input file in the list.
+        */
+       if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
+               return;
+       /*
+        * If can't even open that, we're stuck.  Just quit.
+        */
+       quit(QUIT_ERROR);
+}
+
+       public void
+reopen_curr_ifile()
+{
+       IFILE save_ifile = save_curr_ifile();
+       close_file();
+       reedit_ifile(save_ifile);
+}
+
+/*
+ * Edit standard input.
+ */
+       public int
+edit_stdin()
+{
+       if (isatty(fd0))
+       {
+               error("Missing filename (\"less --help\" for help)", NULL_PARG);
+               quit(QUIT_OK);
+       }
+       return (edit("-"));
+}
+
+/*
+ * Copy a file directly to standard output.
+ * Used if standard output is not a tty.
+ */
+       public void
+cat_file()
+{
+       register int c;
+
+       while ((c = ch_forw_get()) != EOI)
+               putchr(c);
+       flush();
+}
+
+#if LOGFILE
+
+/*
+ * If the user asked for a log file and our input file
+ * is standard input, create the log file.  
+ * We take care not to blindly overwrite an existing file.
+ */
+       public void
+use_logfile(filename)
+       char *filename;
+{
+       register int exists;
+       register int answer;
+       PARG parg;
+
+       if (ch_getflags() & CH_CANSEEK)
+               /*
+                * Can't currently use a log file on a file that can seek.
+                */
+               return;
+
+       /*
+        * {{ We could use access() here. }}
+        */
+       filename = shell_unquote(filename);
+       exists = open(filename, OPEN_READ);
+       close(exists);
+       exists = (exists >= 0);
+
+       /*
+        * Decide whether to overwrite the log file or append to it.
+        * If it doesn't exist we "overwrite" it.
+        */
+       if (!exists || force_logfile)
+       {
+               /*
+                * Overwrite (or create) the log file.
+                */
+               answer = 'O';
+       } else
+       {
+               /*
+                * Ask user what to do.
+                */
+               parg.p_string = filename;
+               answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
+       }
+
+loop:
+       switch (answer)
+       {
+       case 'O': case 'o':
+               /*
+                * Overwrite: create the file.
+                */
+               logfile = creat(filename, 0644);
+               break;
+       case 'A': case 'a':
+               /*
+                * Append: open the file and seek to the end.
+                */
+               logfile = open(filename, OPEN_APPEND);
+               if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK)
+               {
+                       close(logfile);
+                       logfile = -1;
+               }
+               break;
+       case 'D': case 'd':
+               /*
+                * Don't do anything.
+                */
+               free(filename);
+               return;
+       case 'q':
+               quit(QUIT_OK);
+               /*NOTREACHED*/
+       default:
+               /*
+                * Eh?
+                */
+               answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
+               goto loop;
+       }
+
+       if (logfile < 0)
+       {
+               /*
+                * Error in opening logfile.
+                */
+               parg.p_string = filename;
+               error("Cannot write to \"%s\"", &parg);
+               free(filename);
+               return;
+       }
+       free(filename);
+       SET_BINARY(logfile);
+}
+
+#endif