/* * Copyright (c) 1988 Mark Nudleman * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char sccsid[] = "@(#)prim.c 8.1 (Berkeley) 6/6/93"; #endif /* not lint */ #ifndef lint static const char rcsid[] = "$FreeBSD: src/usr.bin/more/prim.c,v 1.10 2000/05/14 03:30:59 hoek Exp $"; #endif /* not lint */ /* * Bookmarking commands. */ #include #include #include #include #include #include #include #include #include #include #include "less.h" extern int sc_width, sc_height; extern int horiz_off; extern int wraplines; extern char *line; extern int retain_below; typedef char * ch_file; /* * A mark is simply a viewing position of a file. This structure is considered * opaque outside of dosetmark(), marks_bookhints(), gomark(), and initmark(). * * An initialised mark pointing at nowhere has hint == file == NULL and * pos == 0. * * An initialised mark pointing at somwhere has all valid fields and possibly * a NULL hint. The hint must be free'd before changing it. */ struct mark { int horiz_off; /* value of horiz_off */ int wraplines; /* value of wraplines */ off_t pos; /* for jump_loc() */ ch_file file; /* file into which mark points */ char *hint; /* hint for human user of the mark's destination */ int linenum; /* optional line number to suplement hint */ }; /* * The table of bookmarks. */ #define NMARKS (27) /* 26 for a-z plus one for quote */ #define LASTMARK (NMARKS-1) /* For quote */ static struct mark marks[NMARKS]; /* * Internal function prototypes. */ static void dosetmark(struct mark *); static void initmark(struct mark *); static int badmark(int); /* * Initialize the mark table to show no marks are set. */ void marks_initmarks(void) { int i; for (i = 0; i < NMARKS; i++) initmark(&marks[i]); } /* * See if a mark letter is valid (between a and z). */ static int badmark(c) int c; { if (c < 'a' || c > 'z') { error("Choose a letter between 'a' and 'z'"); return (1); } return (0); } /* * Set a bookmark. */ setmark(c) int c; /* user identifier for bookmark to set */ { if (badmark(c)) return; dosetmark(&marks[c - 'a']); } /* * This function should be called whenever the file position we are viewing * is changed by a significant amount. The function will record the current * position and remember it for the user. */ lastmark() { dosetmark(&marks[LASTMARK]); } /* * Initialise mark pointed at by mark_p to an empty mark. */ static void initmark (mark_p) struct mark * mark_p; /* mark to initialize */ { mark_p->pos = NULL_POSITION; mark_p->file = NULL; mark_p->hint = NULL; } /* * This function sets the mark pointed at by mark_p to the current viewing * position. */ static void dosetmark(mark_p) struct mark * mark_p; /* bookmark to set to the current position */ { extern char *current_file; extern char *line; off_t npos, t, maxpos; int maxlen; int i; mark_p->pos = position(TOP); mark_p->horiz_off = horiz_off; mark_p->wraplines = wraplines; if (mark_p->file) free(mark_p->file); asprintf(&mark_p->file, "%s", current_file); #define NTOP 4 /* * Find the longest of the top NTOP lines and set that one to * be the hint. * * A different algorithm would be to take the middle line shown * and make that one to be the hint. Perhaps just taking the * top line always would be better. * * There are many places in this program where introducing the * concept of a ``highlight'' line would be useful. * * It's also very debatable which line is the correct line to take * the line number from. */ for (maxlen = i = 0, npos = mark_p->pos; i < NTOP; i++) { t = npos; npos = forw_raw_line (npos); if (strlen(line) >= maxlen) { maxlen = strlen(line); maxpos = t; } } forw_raw_line(maxpos); free (mark_p->hint); mark_p->hint = strdup(line); mark_p->linenum = find_linenum (mark_p->pos); } /* * Put the hints and associated bookmark keys into a temporary file * and run a copy of more(1) over the resulting hint file. * * If an error occurrs, returns -1 and sets the erreur and errstr. * * XXX We don't even come close to handling signals correctly here. */ int marks_bookhints (void) { extern int top_scroll; char *p; int i, n; int fname_width; int fd; FILE *out; char cmd[MAXPATHLEN + 20]; char template[] = "/tmp/more.XXXXXXXXXX"; fd = mkstemp(template); if (fd == -1) goto mkstemp_error; out = fdopen (fd, "w"); if (out == NULL) goto fdopen_error; /* * Find the longest filename. We will find zero if the filenames are * all the same. */ for (fname_width = i = 0; i < NMARKS; i++) { if (marks[i].file && strlen(marks[i].file) >= fname_width) fname_width = strlen(marks[i].file); } for (p = NULL, i = 0; i < NMARKS; i++) { if (p && marks[i].file && strcmp(marks[i].file, p)) break; else if (marks[i].file) p = marks[i].file; } if (i == NMARKS) fname_width = 0; /* * Print-out the header. */ errno = 0; if (fname_width) { n = fprintf (out, "key\t%.*s\tline#\tkeyline\n\n", fname_width, "file"); } else { n = fprintf (out, "key\tline#\tkeyline\n\n"); } if (!n) goto fprintf_error; /* * Print-out the actual bookmarks. */ for (i = 0; i < NMARKS; i++) { int mchar; if (i == LASTMARK) mchar = '\''; else mchar = 'a' + i; if (marks[i].file) { if (fname_width) { n = fprintf (out, "%c\t%.*s\t%d\t%s\n", mchar, fname_width, marks[i].file, marks[i].linenum, marks[i].hint); } else { n = fprintf (out, "%c\t%d\t%s\n", mchar, marks[i].linenum, marks[i].hint); } } if (!n) goto fprintf_error; } fclose (out); /* * Run a recursive copy of more on the hints file. */ snprintf (cmd, sizeof(cmd), "-more -ec %s", template); lsystem(cmd); if (unlink(template) == -1) goto unlink_error; return 0; mkstemp_error: SETERRSTR (E_SYSTEM, "mkstemp(): %s", strerror(errno)); return -1; fdopen_error: SETERRSTR (E_SYSTEM, "fdopen(): %s", strerror(errno)); return -1; unlink_error: SETERRSTR (E_SYSTEM, "unlink(): %s", strerror(errno)); return -1; fprintf_error: if (errno) SETERRSTR (E_SYSTEM, strerror(errno)); else SETERRSTR (E_SYSTEM, "fprintf() failed"); return -1; } /* * Go to a previously set mark. */ gomark(c) int c; { off_t pos; char *file; int new_horiz_off, new_wraplines; extern char *current_file; if (c == '\'') { pos = marks[LASTMARK].pos; if (pos == NULL_POSITION) pos = 0; file = marks[LASTMARK].file; new_horiz_off = marks[LASTMARK].horiz_off; new_wraplines = marks[LASTMARK].wraplines; } else { if (badmark(c)) return; pos = marks[c-'a'].pos; if (pos == NULL_POSITION) { error("mark not set"); return; } file = marks[c-'a'].file; new_horiz_off = marks[c-'a'].horiz_off; new_wraplines = marks[c-'a'].wraplines; } /* * This can only fail if gomark('\'') is called before lastmark() * is called, which is in turn impossible since both edit() and * jump_back() call jump_loc() which calls lastmark() to start * all files. */ assert (file); /* * This can only fail if gomark() is called before any file has * been opened. Calling gomark() before any file has been * opened would be non-sensical. */ assert (current_file); /* * XXX The edit() needs to return success or failure so that we * can abort at this point if edit() fails. */ edit(file, NO_FORCE_OPEN); /* Try to be nice about changing the horizontal scroll and wrapping */ if (new_horiz_off > horiz_off + sc_width / 3 || new_horiz_off < horiz_off - sc_width / 3 || wraplines != new_wraplines || strcmp(file, current_file)) { /* * We should change horiz_off: if we don't change horiz_off * the bookmarked location won't be readily visible. */ /* * A prepaint() doesn't call lastmark() (jump_loc() does), * but we need to call repaint() somewhere since we've * changed the horizontal offset. We don't want to call * jump_loc() followed by repaint() since that represents * more unnecessary screen redrawing than I'm comfortable * with. Manually calling lastmark() here means, however, * that lastmark() is always called even if we scroll only * a few lines --- unlike letting jump_loc() call lastmark() * where lastmark() is only called for jumps of more than * a screenful. A better interface is needed. */ lastmark(); horiz_off = new_horiz_off; wraplines = new_wraplines; prepaint(pos); } else { /* * We can honour the bookmark request without doing any * horizontal scrolling. */ jump_loc(pos); } }