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