1 /*
2  * Copyright (c) 1988 Mark Nudleman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by the University of
17  *	California, Berkeley and its contributors.
18  * 4. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #ifndef lint
36 static char sccsid[] = "@(#)prim.c	8.1 (Berkeley) 6/6/93";
37 #endif /* not lint */
38 
39 #ifndef lint
40 static const char rcsid[] =
41   "$FreeBSD: src/usr.bin/more/prim.c,v 1.10 2000/05/14 03:30:59 hoek Exp $";
42 #endif /* not lint */
43 
44 /*
45  * Bookmarking commands.
46  */
47 
48 #include <sys/param.h>
49 #include <sys/types.h>
50 
51 #include <assert.h>
52 #include <ctype.h>
53 #include <errno.h>
54 #include <limits.h>
55 #include <regex.h>
56 #include <stdio.h>
57 #include <string.h>
58 #include <unistd.h>
59 
60 #include "less.h"
61 
62 extern int sc_width, sc_height;
63 extern int horiz_off;
64 extern int wraplines;
65 extern char *line;
66 extern int retain_below;
67 
68 typedef char * ch_file;
69 
70 /*
71  * A mark is simply a viewing position of a file.  This structure is considered
72  * opaque outside of dosetmark(), marks_bookhints(), gomark(), and initmark().
73  *
74  * An initialised mark pointing at nowhere has hint == file == NULL and
75  * pos == 0.
76  *
77  * An initialised mark pointing at somwhere has all valid fields and possibly
78  * a NULL hint.  The hint must be free'd before changing it.
79  */
80 struct mark {
81 	int	horiz_off;  /* value of horiz_off */
82 	int	wraplines;  /* value of wraplines */
83 	off_t	pos;        /* for jump_loc() */
84 	ch_file	file;       /* file into which mark points */
85 	char	*hint;      /* hint for human user of the mark's destination */
86 	int	linenum;    /* optional line number to suplement hint */
87 };
88 
89 /*
90  * The table of bookmarks.
91  */
92 #define	NMARKS		(27)		/* 26 for a-z plus one for quote */
93 #define	LASTMARK	(NMARKS-1)	/* For quote */
94 static struct mark marks[NMARKS];
95 
96 /*
97  * Internal function prototypes.
98  */
99 static void dosetmark(struct mark *);
100 static void initmark(struct mark *);
101 static int badmark(int);
102 
103 /*
104  * Initialize the mark table to show no marks are set.
105  */
106 void
marks_initmarks(void)107 marks_initmarks(void)
108 {
109 	int i;
110 
111 	for (i = 0;  i < NMARKS;  i++)
112 		initmark(&marks[i]);
113 }
114 
115 /*
116  * See if a mark letter is valid (between a and z).
117  */
118 static int
badmark(c)119 badmark(c)
120 	int c;
121 {
122 	if (c < 'a' || c > 'z') {
123 		error("Choose a letter between 'a' and 'z'");
124 		return (1);
125 	}
126 	return (0);
127 }
128 
129 /*
130  * Set a bookmark.
131  */
132 void
setmark(c)133 setmark(c)
134 	int c;  /* user identifier for bookmark to set */
135 {
136 	if (badmark(c)) return;
137 
138 	dosetmark(&marks[c - 'a']);
139 }
140 
141 /*
142  * This function should be called whenever the file position we are viewing
143  * is changed by a significant amount.  The function will record the current
144  * position and remember it for the user.
145  */
lastmark()146 lastmark()
147 {
148 	dosetmark(&marks[LASTMARK]);
149 }
150 
151 /*
152  * Initialise mark pointed at by mark_p to an empty mark.
153  */
154 static void
initmark(mark_p)155 initmark (mark_p)
156 	struct mark * mark_p;  /* mark to initialize */
157 {
158 	mark_p->pos = NULL_POSITION;
159 	mark_p->file = NULL;
160 	mark_p->hint = NULL;
161 }
162 
163 /*
164  * This function sets the mark pointed at by mark_p to the current viewing
165  * position.
166  */
167 static void
dosetmark(mark_p)168 dosetmark(mark_p)
169 	struct mark * mark_p;  /* bookmark to set to the current position */
170 {
171 	extern char *current_file;
172 	extern char *line;
173 	off_t npos, t, maxpos;
174 	int maxlen;
175 	int i;
176 
177 	mark_p->pos = position(TOP);
178 	mark_p->horiz_off = horiz_off;
179 	mark_p->wraplines = wraplines;
180 	if (mark_p->file) free(mark_p->file);
181 	asprintf(&mark_p->file, "%s", current_file);
182 
183 #define NTOP 4
184 	/*
185 	 * Find the longest of the top NTOP lines and set that one to
186 	 * be the hint.
187 	 *
188 	 * A different algorithm would be to take the middle line shown
189 	 * and make that one to be the hint.  Perhaps just taking the
190 	 * top line always would be better.
191 	 *
192 	 * There are many places in this program where introducing the
193 	 * concept of a ``highlight'' line would be useful.
194 	 *
195 	 * It's also very debatable which line is the correct line to take
196 	 * the line number from.
197 	 */
198 	for (maxlen = i = 0, npos = mark_p->pos; i < NTOP; i++) {
199 		t = npos;
200 		npos = forw_raw_line (npos);
201 		if (strlen(line) >= maxlen) {
202 			maxlen = strlen(line);
203 			maxpos = t;
204 		}
205 	}
206 	forw_raw_line(maxpos);
207 	free (mark_p->hint);
208 	mark_p->hint = strdup(line);
209 	mark_p->linenum = find_linenum (mark_p->pos);
210 }
211 
212 /*
213  * Put the hints and associated bookmark keys into a temporary file
214  * and run a copy of more(1) over the resulting hint file.
215  *
216  * If an error occurrs, returns -1 and sets the erreur and errstr.
217  *
218  * XXX We don't even come close to handling signals correctly here.
219  */
220 int
marks_bookhints(void)221 marks_bookhints (void)
222 {
223 	extern int top_scroll;
224 	char *p;
225 	int i, n;
226 	int fname_width;
227 	int fd;
228 	FILE *out;
229 	char cmd[MAXPATHLEN + 20];
230 	char template[] = "/tmp/more.XXXXXXXXXX";
231 
232 	fd = mkstemp(template);
233 	if (fd == -1) goto mkstemp_error;
234 	out = fdopen (fd, "w");
235 	if (out == NULL) goto fdopen_error;
236 
237 	/*
238 	 * Find the longest filename.  We will find zero if the filenames are
239 	 * all the same.
240 	 */
241 	for (fname_width = i = 0; i < NMARKS; i++) {
242 		if (marks[i].file && strlen(marks[i].file) >= fname_width)
243 			fname_width = strlen(marks[i].file);
244 	}
245 	for (p = NULL, i = 0; i < NMARKS; i++) {
246 		if (p && marks[i].file && strcmp(marks[i].file, p))
247 			break;
248 		else if (marks[i].file)
249 			p = marks[i].file;
250 	}
251 	if (i == NMARKS)
252 		fname_width = 0;
253 
254 	/*
255 	 * Print-out the header.
256 	 */
257 	errno = 0;
258 	if (fname_width) {
259 		n = fprintf (out, "key\t%.*s\tline#\tkeyline\n\n",
260 		    fname_width, "file");
261 	} else {
262 		n = fprintf (out, "key\tline#\tkeyline\n\n");
263 	}
264 	if (!n) goto fprintf_error;
265 
266 	/*
267 	 * Print-out the actual bookmarks.
268 	 */
269 	for (i = 0; i < NMARKS; i++)
270 	{
271 		int mchar;
272 		if (i == LASTMARK)
273 			mchar = '\'';
274 		else
275 			mchar = 'a' + i;
276 
277 		if (marks[i].file) {
278 			if (fname_width) {
279 				n = fprintf (out, "%c\t%.*s\t%d\t%s\n",
280 			    	mchar, fname_width, marks[i].file,
281 				    marks[i].linenum, marks[i].hint);
282 			} else {
283 				n = fprintf (out, "%c\t%d\t%s\n",
284 				    mchar, marks[i].linenum, marks[i].hint);
285 			}
286 		}
287 		if (!n) goto fprintf_error;
288 	}
289 	fclose (out);
290 
291 	/*
292  	 * Run a recursive copy of more on the hints file.
293 	 */
294 	snprintf (cmd, sizeof(cmd), "-more -ec %s", template);
295 	lsystem(cmd);
296 	if (unlink(template) == -1)
297 		goto unlink_error;
298 
299 	return 0;
300 
301 
302 mkstemp_error:
303 	SETERRSTR (E_SYSTEM, "mkstemp(): %s", strerror(errno));
304 	return -1;
305 fdopen_error:
306 	SETERRSTR (E_SYSTEM, "fdopen(): %s", strerror(errno));
307 	return -1;
308 unlink_error:
309 	SETERRSTR (E_SYSTEM, "unlink(): %s", strerror(errno));
310 	return -1;
311 fprintf_error:
312 	if (errno)
313 		SETERRSTR (E_SYSTEM, strerror(errno));
314 	else
315 		SETERRSTR (E_SYSTEM, "fprintf() failed");
316 	return -1;
317 }
318 
319 /*
320  * Go to a previously set mark.
321  */
322 void
gomark(c)323 gomark(c)
324 	int c;
325 {
326 	off_t pos;
327 	char *file;
328 	int new_horiz_off, new_wraplines;
329 	extern char *current_file;
330 
331 	if (c == '\'') {
332 		pos = marks[LASTMARK].pos;
333 		if (pos == NULL_POSITION)
334 			pos = 0;
335 		file = marks[LASTMARK].file;
336 		new_horiz_off = marks[LASTMARK].horiz_off;
337 		new_wraplines = marks[LASTMARK].wraplines;
338 	} else {
339 		if (badmark(c))
340 			return;
341 		pos = marks[c-'a'].pos;
342 		if (pos == NULL_POSITION) {
343 			error("mark not set");
344 			return;
345 		}
346 		file = marks[c-'a'].file;
347 		new_horiz_off = marks[c-'a'].horiz_off;
348 		new_wraplines = marks[c-'a'].wraplines;
349 	}
350 
351 	/*
352 	 * This can only fail if gomark('\'') is called before lastmark()
353 	 * is called, which is in turn impossible since both edit() and
354 	 * jump_back() call jump_loc() which calls lastmark() to start
355 	 * all files.
356 	 */
357 	assert (file);
358 
359 	/*
360 	 * This can only fail if gomark() is called before any file has
361 	 * been opened.  Calling gomark() before any file has been
362 	 * opened would be non-sensical.
363 	 */
364 	assert (current_file);
365 
366 	/*
367 	 * XXX The edit() needs to return success or failure so that we
368 	 *     can abort at this point if edit() fails.
369 	 */
370 	edit(file, NO_FORCE_OPEN);
371 
372 	/* Try to be nice about changing the horizontal scroll and wrapping */
373 	if (new_horiz_off > horiz_off + sc_width / 3 ||
374 	    new_horiz_off < horiz_off - sc_width / 3 ||
375 	    wraplines != new_wraplines || strcmp(file, current_file)) {
376 		/*
377 		 * We should change horiz_off: if we don't change horiz_off
378 		 * the bookmarked location won't be readily visible.
379 		 */
380 
381 		/*
382 		 * A prepaint() doesn't call lastmark() (jump_loc() does),
383 		 * but we need to call repaint() somewhere since we've
384 		 * changed the horizontal offset.  We don't want to call
385 		 * jump_loc() followed by repaint() since that represents
386 		 * more unnecessary screen redrawing than I'm comfortable
387 		 * with.  Manually calling lastmark() here means, however,
388 		 * that lastmark() is always called even if we scroll only
389 		 * a few lines --- unlike letting jump_loc() call lastmark()
390 		 * where lastmark() is only called for jumps of more than
391 		 * a screenful.  A better interface is needed.
392 		 */
393 		lastmark();
394 
395 		horiz_off = new_horiz_off;
396 		wraplines = new_wraplines;
397 		prepaint(pos);
398 	} else {
399 		/*
400 		 * We can honour the bookmark request without doing any
401 		 * horizontal scrolling.
402 		 */
403 		jump_loc(pos);
404 	}
405 }
406