output.c revision 170257
1/*
2 * Copyright (C) 1984-2007  Mark Nudelman
3 *
4 * You may distribute under the terms of either the GNU General Public
5 * License or the Less License, as specified in the README file.
6 *
7 * For more information about less, or for information on how to
8 * contact the author, see the README file.
9 */
10
11
12/*
13 * High level routines dealing with the output to the screen.
14 */
15
16#include "less.h"
17#if MSDOS_COMPILER==WIN32C
18#include "windows.h"
19#endif
20
21public int errmsgs;	/* Count of messages displayed by error() */
22public int need_clr;
23public int final_attr;
24public int at_prompt;
25
26extern int sigs;
27extern int sc_width;
28extern int so_s_width, so_e_width;
29extern int screen_trashed;
30extern int any_display;
31extern int is_tty;
32extern int oldbot;
33
34#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
35extern int ctldisp;
36extern int nm_fg_color, nm_bg_color;
37extern int bo_fg_color, bo_bg_color;
38extern int ul_fg_color, ul_bg_color;
39extern int so_fg_color, so_bg_color;
40extern int bl_fg_color, bl_bg_color;
41#endif
42
43/*
44 * Display the line which is in the line buffer.
45 */
46	public void
47put_line()
48{
49	register int c;
50	register int i;
51	int a;
52
53	if (ABORT_SIGS())
54	{
55		/*
56		 * Don't output if a signal is pending.
57		 */
58		screen_trashed = 1;
59		return;
60	}
61
62	final_attr = AT_NORMAL;
63
64	for (i = 0;  (c = gline(i, &a)) != '\0';  i++)
65	{
66		at_switch(a);
67		final_attr = a;
68		if (c == '\b')
69			putbs();
70		else
71			putchr(c);
72	}
73
74	at_exit();
75}
76
77static char obuf[OUTBUF_SIZE];
78static char *ob = obuf;
79
80/*
81 * Flush buffered output.
82 *
83 * If we haven't displayed any file data yet,
84 * output messages on error output (file descriptor 2),
85 * otherwise output on standard output (file descriptor 1).
86 *
87 * This has the desirable effect of producing all
88 * error messages on error output if standard output
89 * is directed to a file.  It also does the same if
90 * we never produce any real output; for example, if
91 * the input file(s) cannot be opened.  If we do
92 * eventually produce output, code in edit() makes
93 * sure these messages can be seen before they are
94 * overwritten or scrolled away.
95 */
96	public void
97flush()
98{
99	register int n;
100	register int fd;
101
102	n = ob - obuf;
103	if (n == 0)
104		return;
105#if MSDOS_COMPILER==WIN32C
106	if (is_tty && any_display)
107	{
108		char *op;
109		DWORD nwritten = 0;
110		CONSOLE_SCREEN_BUFFER_INFO scr;
111		int row;
112		int col;
113		int olen;
114		extern HANDLE con_out;
115
116		olen = ob - obuf;
117		/*
118		 * There is a bug in Win32 WriteConsole() if we're
119		 * writing in the last cell with a different color.
120		 * To avoid color problems in the bottom line,
121		 * we scroll the screen manually, before writing.
122		 */
123		GetConsoleScreenBufferInfo(con_out, &scr);
124		col = scr.dwCursorPosition.X;
125		row = scr.dwCursorPosition.Y;
126		for (op = obuf;  op < obuf + olen;  op++)
127		{
128			if (*op == '\n')
129			{
130				col = 0;
131				row++;
132			} else if (*op == '\r')
133			{
134				col = 0;
135			} else
136			{
137				col++;
138				if (col >= sc_width)
139				{
140					col = 0;
141					row++;
142				}
143			}
144		}
145		if (row > scr.srWindow.Bottom)
146			win32_scroll_up(row - scr.srWindow.Bottom);
147		WriteConsole(con_out, obuf, olen, &nwritten, NULL);
148		ob = obuf;
149		return;
150	}
151#else
152#if MSDOS_COMPILER==MSOFTC
153	if (is_tty && any_display)
154	{
155		*ob = '\0';
156		_outtext(obuf);
157		ob = obuf;
158		return;
159	}
160#else
161#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
162	if (is_tty && any_display)
163	{
164		*ob = '\0';
165		if (ctldisp != OPT_ONPLUS)
166			cputs(obuf);
167		else
168		{
169			/*
170			 * Look for SGR escape sequences, and convert them
171			 * to color commands.  Replace bold, underline,
172			 * and italic escapes into colors specified via
173			 * the -D command-line option.
174			 */
175			char *anchor, *p, *p_next;
176			int buflen = ob - obuf;
177			unsigned char fg, bg, norm_attr;
178			/*
179			 * Only dark colors mentioned here, so that
180			 * bold has visible effect.
181			 */
182			static enum COLORS screen_color[] = {
183				BLACK, RED, GREEN, BROWN,
184				BLUE, MAGENTA, CYAN, LIGHTGRAY
185			};
186
187			/* Normal text colors are used as baseline. */
188			bg = nm_bg_color & 0xf;
189			fg = nm_fg_color & 0xf;
190			norm_attr = (bg << 4) | fg;
191			for (anchor = p_next = obuf;
192			     (p_next = memchr (p_next, ESC,
193					       buflen - (p_next - obuf)))
194			       != NULL; )
195			{
196				p = p_next;
197
198				/*
199				 * Handle the null escape sequence
200				 * (ESC-[m), which is used to restore
201				 * the original color.
202				 */
203				if (p[1] == '[' && is_ansi_end(p[2]))
204				{
205					textattr(norm_attr);
206					p += 3;
207					anchor = p_next = p;
208					continue;
209				}
210
211				if (p[1] == '[')	/* "Esc-[" sequence */
212				{
213					/*
214					 * If some chars seen since
215					 * the last escape sequence,
216					 * write it out to the screen
217					 * using current text attributes.
218					 */
219					if (p > anchor)
220					{
221						*p = '\0';
222						cputs (anchor);
223						*p = ESC;
224						anchor = p;
225					}
226					p += 2;
227					p_next = p;
228					while (!is_ansi_end(*p))
229					{
230						char *q;
231						long code = strtol(p, &q, 10);
232
233						if (!*q)
234						{
235							/*
236							 * Incomplete sequence.
237							 * Leave it unprocessed
238							 * in the buffer.
239							 */
240							int slop = q - anchor;
241							strcpy(obuf, anchor);
242							ob = &obuf[slop];
243							return;
244						}
245
246						if (q == p
247						    || code > 49 || code < 0
248						    || (!is_ansi_end(*q)
249							&& *q != ';'))
250						{
251							p_next = q;
252							break;
253						}
254						if (*q == ';')
255							q++;
256
257						switch (code)
258						{
259						case 1:	/* bold on */
260							fg = bo_fg_color;
261							bg = bo_bg_color;
262							break;
263						case 3:	/* italic on */
264							fg = so_fg_color;
265							bg = so_bg_color;
266							break;
267						case 4:	/* underline on */
268							fg = ul_fg_color;
269							bg = ul_bg_color;
270							break;
271						case 8:	/* concealed on */
272							fg = (bg & 7) | 8;
273							break;
274						case 0:	/* all attrs off */
275						case 22:/* bold off */
276						case 23:/* italic off */
277						case 24:/* underline off */
278							fg = nm_fg_color;
279							bg = nm_bg_color;
280							break;
281						case 30: case 31: case 32:
282						case 33: case 34: case 35:
283						case 36: case 37:
284							fg = (fg & 8) | (screen_color[code - 30]);
285							break;
286						case 39: /* default fg */
287							fg = nm_fg_color;
288							break;
289						case 40: case 41: case 42:
290						case 43: case 44: case 45:
291						case 46: case 47:
292							bg = (bg & 8) | (screen_color[code - 40]);
293							break;
294						case 49: /* default fg */
295							bg = nm_bg_color;
296							break;
297						}
298						p = q;
299					}
300					if (is_ansi_end(*p) && p > p_next)
301					{
302						bg &= 15;
303						fg &= 15;
304						textattr ((bg << 4)| fg);
305						p_next = anchor = p + 1;
306					} else
307						break;
308				} else
309					p_next++;
310			}
311
312			/* Output what's left in the buffer.  */
313			cputs (anchor);
314		}
315		ob = obuf;
316		return;
317	}
318#endif
319#endif
320#endif
321	fd = (any_display) ? 1 : 2;
322	if (write(fd, obuf, n) != n)
323		screen_trashed = 1;
324	ob = obuf;
325}
326
327/*
328 * Output a character.
329 */
330	public int
331putchr(c)
332	int c;
333{
334#if 0 /* fake UTF-8 output for testing */
335	extern int utf_mode;
336	if (utf_mode)
337	{
338		static char ubuf[MAX_UTF_CHAR_LEN];
339		static int ubuf_len = 0;
340		static int ubuf_index = 0;
341		if (ubuf_len == 0)
342		{
343			ubuf_len = utf_len(c);
344			ubuf_index = 0;
345		}
346		ubuf[ubuf_index++] = c;
347		if (ubuf_index < ubuf_len)
348			return c;
349		c = get_wchar(ubuf) & 0xFF;
350		ubuf_len = 0;
351	}
352#endif
353	if (need_clr)
354	{
355		need_clr = 0;
356		clear_bot();
357	}
358#if MSDOS_COMPILER
359	if (c == '\n' && is_tty)
360	{
361		/* remove_top(1); */
362		putchr('\r');
363	}
364#else
365#ifdef _OSK
366	if (c == '\n' && is_tty)  /* In OS-9, '\n' == 0x0D */
367		putchr(0x0A);
368#endif
369#endif
370	/*
371	 * Some versions of flush() write to *ob, so we must flush
372	 * when we are still one char from the end of obuf.
373	 */
374	if (ob >= &obuf[sizeof(obuf)-1])
375		flush();
376	*ob++ = c;
377	at_prompt = 0;
378	return (c);
379}
380
381/*
382 * Output a string.
383 */
384	public void
385putstr(s)
386	register char *s;
387{
388	while (*s != '\0')
389		putchr(*s++);
390}
391
392
393/*
394 * Convert an integral type to a string.
395 */
396#define TYPE_TO_A_FUNC(funcname, type) \
397void funcname(num, buf) \
398	type num; \
399	char *buf; \
400{ \
401	int neg = (num < 0); \
402	char tbuf[INT_STRLEN_BOUND(num)+2]; \
403	register char *s = tbuf + sizeof(tbuf); \
404	if (neg) num = -num; \
405	*--s = '\0'; \
406	do { \
407		*--s = (num % 10) + '0'; \
408	} while ((num /= 10) != 0); \
409	if (neg) *--s = '-'; \
410	strcpy(buf, s); \
411}
412
413TYPE_TO_A_FUNC(postoa, POSITION)
414TYPE_TO_A_FUNC(linenumtoa, LINENUM)
415TYPE_TO_A_FUNC(inttoa, int)
416
417/*
418 * Output an integer in a given radix.
419 */
420	static int
421iprint_int(num)
422	int num;
423{
424	char buf[INT_STRLEN_BOUND(num)];
425
426	inttoa(num, buf);
427	putstr(buf);
428	return (strlen(buf));
429}
430
431/*
432 * Output a line number in a given radix.
433 */
434	static int
435iprint_linenum(num)
436	LINENUM num;
437{
438	char buf[INT_STRLEN_BOUND(num)];
439
440	linenumtoa(num, buf);
441	putstr(buf);
442	return (strlen(buf));
443}
444
445/*
446 * This function implements printf-like functionality
447 * using a more portable argument list mechanism than printf's.
448 */
449	static int
450less_printf(fmt, parg)
451	register char *fmt;
452	PARG *parg;
453{
454	register char *s;
455	register int col;
456
457	col = 0;
458	while (*fmt != '\0')
459	{
460		if (*fmt != '%')
461		{
462			putchr(*fmt++);
463			col++;
464		} else
465		{
466			++fmt;
467			switch (*fmt++)
468			{
469			case 's':
470				s = parg->p_string;
471				parg++;
472				while (*s != '\0')
473				{
474					putchr(*s++);
475					col++;
476				}
477				break;
478			case 'd':
479				col += iprint_int(parg->p_int);
480				parg++;
481				break;
482			case 'n':
483				col += iprint_linenum(parg->p_linenum);
484				parg++;
485				break;
486			}
487		}
488	}
489	return (col);
490}
491
492/*
493 * Get a RETURN.
494 * If some other non-trivial char is pressed, unget it, so it will
495 * become the next command.
496 */
497	public void
498get_return()
499{
500	int c;
501
502#if ONLY_RETURN
503	while ((c = getchr()) != '\n' && c != '\r')
504		bell();
505#else
506	c = getchr();
507	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
508		ungetcc(c);
509#endif
510}
511
512/*
513 * Output a message in the lower left corner of the screen
514 * and wait for carriage return.
515 */
516	public void
517error(fmt, parg)
518	char *fmt;
519	PARG *parg;
520{
521	int col = 0;
522	static char return_to_continue[] = "  (press RETURN)";
523
524	errmsgs++;
525
526	if (any_display && is_tty)
527	{
528		if (!oldbot)
529			squish_check();
530		at_exit();
531		clear_bot();
532		at_enter(AT_STANDOUT);
533		col += so_s_width;
534	}
535
536	col += less_printf(fmt, parg);
537
538	if (!(any_display && is_tty))
539	{
540		putchr('\n');
541		return;
542	}
543
544	putstr(return_to_continue);
545	at_exit();
546	col += sizeof(return_to_continue) + so_e_width;
547
548	get_return();
549	lower_left();
550
551	if (col >= sc_width)
552		/*
553		 * Printing the message has probably scrolled the screen.
554		 * {{ Unless the terminal doesn't have auto margins,
555		 *    in which case we just hammered on the right margin. }}
556		 */
557		screen_trashed = 1;
558
559	flush();
560}
561
562static char intr_to_abort[] = "... (interrupt to abort)";
563
564/*
565 * Output a message in the lower left corner of the screen
566 * and don't wait for carriage return.
567 * Usually used to warn that we are beginning a potentially
568 * time-consuming operation.
569 */
570	public void
571ierror(fmt, parg)
572	char *fmt;
573	PARG *parg;
574{
575	at_exit();
576	clear_bot();
577	at_enter(AT_STANDOUT);
578	(void) less_printf(fmt, parg);
579	putstr(intr_to_abort);
580	at_exit();
581	flush();
582	need_clr = 1;
583}
584
585/*
586 * Output a message in the lower left corner of the screen
587 * and return a single-character response.
588 */
589	public int
590query(fmt, parg)
591	char *fmt;
592	PARG *parg;
593{
594	register int c;
595	int col = 0;
596
597	if (any_display && is_tty)
598		clear_bot();
599
600	(void) less_printf(fmt, parg);
601	c = getchr();
602
603	if (!(any_display && is_tty))
604	{
605		putchr('\n');
606		return (c);
607	}
608
609	lower_left();
610	if (col >= sc_width)
611		screen_trashed = 1;
612	flush();
613
614	return (c);
615}
616