xref: /openbsd/usr.bin/less/output.c (revision fc61954a)
1 /*
2  * Copyright (C) 1984-2012  Mark Nudelman
3  * Modified for use with illumos by Garrett D'Amore.
4  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
5  *
6  * You may distribute under the terms of either the GNU General Public
7  * License or the Less License, as specified in the README file.
8  *
9  * For more information, see the README file.
10  */
11 
12 /*
13  * High level routines dealing with the output to the screen.
14  */
15 
16 #include "less.h"
17 
18 int errmsgs;	/* Count of messages displayed by error() */
19 
20 extern volatile sig_atomic_t sigs;
21 extern int sc_width;
22 extern int so_s_width, so_e_width;
23 extern int screen_trashed;
24 extern int any_display;
25 extern int is_tty;
26 extern int oldbot;
27 
28 static int need_clr;
29 
30 /*
31  * Display the line which is in the line buffer.
32  */
33 void
34 put_line(void)
35 {
36 	int c;
37 	int i;
38 	int a;
39 
40 	if (ABORT_SIGS()) {
41 		/*
42 		 * Don't output if a signal is pending.
43 		 */
44 		screen_trashed = 1;
45 		return;
46 	}
47 
48 	for (i = 0; (c = gline(i, &a)) != '\0'; i++) {
49 		at_switch(a);
50 		if (c == '\b')
51 			putbs();
52 		else
53 			(void) putchr(c);
54 	}
55 
56 	at_exit();
57 }
58 
59 static char obuf[OUTBUF_SIZE];
60 static char *ob = obuf;
61 
62 /*
63  * Flush buffered output.
64  *
65  * If we haven't displayed any file data yet,
66  * output messages on error output (file descriptor 2),
67  * otherwise output on standard output (file descriptor 1).
68  *
69  * This has the desirable effect of producing all
70  * error messages on error output if standard output
71  * is directed to a file.  It also does the same if
72  * we never produce any real output; for example, if
73  * the input file(s) cannot be opened.  If we do
74  * eventually produce output, code in edit() makes
75  * sure these messages can be seen before they are
76  * overwritten or scrolled away.
77  */
78 void
79 flush(int ignore_errors)
80 {
81 	int n;
82 	int fd;
83 	ssize_t nwritten;
84 
85 	n = (intptr_t)ob - (intptr_t)obuf;
86 	if (n == 0)
87 		return;
88 
89 	fd = (any_display) ? STDOUT_FILENO : STDERR_FILENO;
90 	nwritten = write(fd, obuf, n);
91 	if (nwritten != n) {
92 		if (nwritten == -1 && !ignore_errors)
93 			quit(QUIT_ERROR);
94 		screen_trashed = 1;
95 	}
96 	ob = obuf;
97 }
98 
99 /*
100  * Output a character.
101  */
102 int
103 putchr(int c)
104 {
105 	if (need_clr) {
106 		need_clr = 0;
107 		clear_bot();
108 	}
109 	/*
110 	 * Some versions of flush() write to *ob, so we must flush
111 	 * when we are still one char from the end of obuf.
112 	 */
113 	if (ob >= &obuf[sizeof (obuf)-1])
114 		flush(0);
115 	*ob++ = (char)c;
116 	return (c);
117 }
118 
119 /*
120  * Output a string.
121  */
122 void
123 putstr(const char *s)
124 {
125 	while (*s != '\0')
126 		(void) putchr(*s++);
127 }
128 
129 
130 /*
131  * Convert an integral type to a string.
132  */
133 #define	TYPE_TO_A_FUNC(funcname, type)		\
134 void						\
135 funcname(type num, char *buf, size_t len)	\
136 {						\
137 	int neg = (num < 0);			\
138 	char tbuf[23];	\
139 	char *s = tbuf + sizeof (tbuf);		\
140 	if (neg)				\
141 		num = -num;			\
142 	*--s = '\0';				\
143 	do {					\
144 		*--s = (num % 10) + '0';	\
145 	} while ((num /= 10) != 0);		\
146 	if (neg)				\
147 		 *--s = '-';			\
148 	(void) strlcpy(buf, s, len);		\
149 }
150 
151 TYPE_TO_A_FUNC(postoa, off_t)
152 TYPE_TO_A_FUNC(inttoa, int)
153 
154 /*
155  * Output an integer in a given radix.
156  */
157 static int
158 iprint_int(int num)
159 {
160 	char buf[11];
161 
162 	inttoa(num, buf, sizeof (buf));
163 	putstr(buf);
164 	return (strlen(buf));
165 }
166 
167 /*
168  * Output a line number in a given radix.
169  */
170 static int
171 iprint_linenum(off_t num)
172 {
173 	char buf[21];
174 
175 	postoa(num, buf, sizeof(buf));
176 	putstr(buf);
177 	return (strlen(buf));
178 }
179 
180 /*
181  * This function implements printf-like functionality
182  * using a more portable argument list mechanism than printf's.
183  */
184 static int
185 less_printf(const char *fmt, PARG *parg)
186 {
187 	char *s;
188 	int col;
189 
190 	col = 0;
191 	while (*fmt != '\0') {
192 		if (*fmt != '%') {
193 			(void) putchr(*fmt++);
194 			col++;
195 		} else {
196 			++fmt;
197 			switch (*fmt++) {
198 			case 's':
199 				s = parg->p_string;
200 				parg++;
201 				while (*s != '\0') {
202 					(void) putchr(*s++);
203 					col++;
204 				}
205 				break;
206 			case 'd':
207 				col += iprint_int(parg->p_int);
208 				parg++;
209 				break;
210 			case 'n':
211 				col += iprint_linenum(parg->p_linenum);
212 				parg++;
213 				break;
214 			}
215 		}
216 	}
217 	return (col);
218 }
219 
220 /*
221  * Get a RETURN.
222  * If some other non-trivial char is pressed, unget it, so it will
223  * become the next command.
224  */
225 void
226 get_return(void)
227 {
228 	int c;
229 
230 	c = getchr();
231 	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
232 		ungetcc(c);
233 }
234 
235 /*
236  * Output a message in the lower left corner of the screen
237  * and wait for carriage return.
238  */
239 void
240 error(const char *fmt, PARG *parg)
241 {
242 	int col = 0;
243 	static char return_to_continue[] = "  (press RETURN)";
244 
245 	errmsgs++;
246 
247 	if (any_display && is_tty) {
248 		if (!oldbot)
249 			squish_check();
250 		at_exit();
251 		clear_bot();
252 		at_enter(AT_STANDOUT);
253 		col += so_s_width;
254 	}
255 
256 	col += less_printf(fmt, parg);
257 
258 	if (!(any_display && is_tty)) {
259 		(void) putchr('\n');
260 		return;
261 	}
262 
263 	putstr(return_to_continue);
264 	at_exit();
265 	col += sizeof (return_to_continue) + so_e_width;
266 
267 	get_return();
268 	lower_left();
269 	clear_eol();
270 
271 	if (col >= sc_width)
272 		/*
273 		 * Printing the message has probably scrolled the screen.
274 		 * {{ Unless the terminal doesn't have auto margins,
275 		 *    in which case we just hammered on the right margin. }}
276 		 */
277 		screen_trashed = 1;
278 
279 	flush(0);
280 }
281 
282 static char intr_to_abort[] = "... (interrupt to abort)";
283 
284 /*
285  * Output a message in the lower left corner of the screen
286  * and don't wait for carriage return.
287  * Usually used to warn that we are beginning a potentially
288  * time-consuming operation.
289  */
290 void
291 ierror(const char *fmt, PARG *parg)
292 {
293 	at_exit();
294 	clear_bot();
295 	at_enter(AT_STANDOUT);
296 	(void) less_printf(fmt, parg);
297 	putstr(intr_to_abort);
298 	at_exit();
299 	flush(0);
300 	need_clr = 1;
301 }
302 
303 /*
304  * Output a message in the lower left corner of the screen
305  * and return a single-character response.
306  */
307 int
308 query(const char *fmt, PARG *parg)
309 {
310 	int c;
311 	int col = 0;
312 
313 	if (any_display && is_tty)
314 		clear_bot();
315 
316 	(void) less_printf(fmt, parg);
317 	c = getchr();
318 
319 	if (!(any_display && is_tty)) {
320 		(void) putchr('\n');
321 		return (c);
322 	}
323 
324 	lower_left();
325 	if (col >= sc_width)
326 		screen_trashed = 1;
327 	flush(0);
328 
329 	return (c);
330 }
331