1 /*
2  This file is part of MOST.
3 
4  Copyright (c) 1991, 1999, 2002, 2005-2018, 2019 John E. Davis
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the Free
8  Software Foundation; either version 2 of the License, or (at your option)
9  any later version.
10 
11  This program is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  more details.
15 
16  You should have received a copy of the GNU General Public License along
17  with this program; if not, write to the Free Software Foundation, Inc., 675
18  Mass Ave, Cambridge, MA 02139, USA.
19 */
20 #include "config.h"
21 
22 #include <stdio.h>
23 #include <string.h>
24 
25 #include <ctype.h>
26 #include <slang.h>
27 #include "jdmacros.h"
28 
29 #include "most.h"
30 #include "line.h"
31 #include "window.h"
32 #include "display.h"
33 
34 int Most_Tab_Width = 8;
35 
36 int Most_Selective_Display = 0;
37 int Most_Show_Wrap_Marker = 1;
38 
39 #define IS_BYTE_PRINTABLE(b) \
40    ((((b) >= ' ') && ((b) < 0x7F)) \
41        || ((Most_UTF8_Mode == 0) && ((b) >= SLsmg_Display_Eight_Bit)))
42 
43 /* take 16 binary characters and put them in displayable form */
binary_format_line(unsigned char * beg,unsigned char * end,char * buf)44 static void binary_format_line (unsigned char *beg, unsigned char *end,
45 			       char *buf)
46 {
47    unsigned char *b;
48    char *s, *s1;
49    unsigned char ch;
50    int count;
51 
52    count = 0;
53    b = beg;
54    s = buf;
55 
56    while (b < end)
57      {
58 	if (count == 4)
59 	  {
60 	     *s++ = ' ';
61 	     count = 0;
62 	  }
63 	count++;
64 
65 	ch = *b++;
66 
67 	if ((Most_V_Opt == 0)
68 	    || (ch & 0x80))
69 	  {
70 	     sprintf (s, "%02X", ch);
71 	     s += 2;
72 	     continue;
73 	  }
74 
75 	if ((ch >= ' ') && (ch < 0x7F))
76 	  {
77 	     *s++ = ' ';
78 	     *s++ = (char) ch;
79 	     continue;
80 	  }
81 
82 	*s++ = '^';
83 	if (ch < ' ') ch += '@';
84 	else ch = '?';
85 	*s++ = ch;
86      }
87 
88    s1 = buf + (9 * 4) + 4;
89    while (s < s1)
90      *s++ = ' ';
91 
92    b = beg;
93    while (b < end)
94      {
95         ch = *b++;
96 	if (IS_BYTE_PRINTABLE(ch))
97 	  {
98 	     *s++ = ch;
99 	     continue;
100 	  }
101 	*s++ = '.';
102      }
103    *s = 0;
104 }
105 
output_binary_formatted_line(void)106 static void output_binary_formatted_line (void)
107 {
108    unsigned char *beg, *end;
109    char buf[256];
110 
111    beg = Most_Beg + Most_C_Offset;
112    end = beg + 16;
113 
114    if (end > Most_Eob) end = Most_Eob;
115 
116    sprintf (buf, "0x%08lX: ", (unsigned long) Most_C_Offset);
117    binary_format_line (beg, end, buf + 12);
118    SLsmg_write_string (buf);
119    SLsmg_erase_eol ();
120 }
121 
122 /* Here *begp points to the char after \e.
123  * The general escape sequence parsed here is assumed to look like:
124  *   \e[ XX ; ... m
125  * If 30 <= XX <= 37, then it specifies the foreground color
126  * If 40 <= XX <= 47, then a background color is specified
127  * If  0 <= XX <= 8, then an attribute (e.g, 8) is specified.
128  * These numbers will be encoded as:
129  *  offset + (FG-30 + 8*(BG-40 + 9*attribute))
130  */
most_parse_color_escape(unsigned char ** begp,unsigned char * end,int * colorp)131 int most_parse_color_escape (unsigned char **begp, unsigned char *end, int *colorp)
132 {
133    unsigned char *beg = *begp;
134    int fg = 38, bg = 48, at = 0;
135    int xx;
136 
137    if ((beg >= end) || (*beg != '['))
138      return -1;
139 
140    beg++; /* skip [ */
141 #if 1
142    if ((beg < end) && (*beg == 'K'))
143      {
144        if (colorp != NULL) *colorp = -1;
145        *begp = beg + 1;
146        return 0;
147      }
148 #endif
149 
150    while (1)
151      {
152 	xx = 0;
153 	while ((beg < end) && isdigit (*beg))
154 	  {
155 	     xx = xx*10 + (*beg - '0');
156 	     beg++;
157 	  }
158 	if ((xx >= 0) && (xx <= 8))
159 	  at = xx;
160 	else if ((xx >= 20) && (xx <= 28))
161 	  xx = 0;
162 	else if ((xx >= 30) && (xx <= 37))
163 	  fg = xx;
164 	else if ((xx >= 40) && (xx <= 47))
165 	  bg = xx;
166 	else return -1;
167 
168 	if ((beg < end) && (*beg == ';'))
169 	  {
170 	     beg++;
171 	     continue;
172 	  }
173 
174 	if ((beg < end) && ((*beg == 'm') || (*beg == ']')))
175 	  {
176 	     *begp = beg + 1;
177 	     if (colorp != NULL)
178 	       {
179 		  if ((fg != 38) || (bg != 48))
180 		    xx = ((fg-30) + 9*((bg-40) + 9*at));
181 		  if (xx != 0)
182 		    xx += MOST_EMBEDDED_COLOR_OFFSET;
183 		  *colorp = xx;
184 	       }
185 	     return 0;
186 	  }
187 	return -1;
188      }
189 }
190 
191 typedef struct
192 {
193    unsigned char *bytes;
194    unsigned char byte;		       /* used if bytes is NULL */
195    unsigned int len;
196    int color;
197 }
198 Multibyte_Cell_Type;
199 
most_analyse_line(unsigned char * begg,unsigned char * endd,Multibyte_Cell_Type * cells,unsigned int num_cols,int * start_colorp)200 static int most_analyse_line (unsigned char *begg, unsigned char *endd,
201 			      Multibyte_Cell_Type *cells, unsigned int num_cols, int *start_colorp)
202 {
203    unsigned char *beg, *end;
204    unsigned int min_col, max_col, prev_width;
205    unsigned int col, max_col_reached;
206    int default_attr;
207    Multibyte_Cell_Type *cell, *max_cell;
208 
209    beg = begg;
210    end = endd;
211    col = max_col_reached = 0;
212    cell = cells;
213    max_cell = cell;
214    min_col = Most_Column - 1;
215    max_col = min_col + num_cols;
216 
217    default_attr = *start_colorp;
218    prev_width = 1;
219    while (beg < end)
220      {
221 	int attr = default_attr;
222 	unsigned char ch;
223 	unsigned char *pch = beg++;
224 	char buf[16];
225 
226 	if ('\n' == (ch = *pch))
227 	  break;
228 
229 	if ((ch == '\r') && (Most_V_Opt == 0))
230 	  {
231 	     if (col > max_col_reached) max_col_reached = col;
232 	     col = 0;
233 	     prev_width = 1;
234 	     continue;
235 	  }
236 
237 	if ((ch == '\b') && (Most_V_Opt == 0))
238 	  {
239 	     if (col > max_col_reached) max_col_reached = col;
240 	     if (col < prev_width)
241 	       col = 0;
242 	     else
243 	       col -= prev_width;
244 	     continue;
245 	  }
246 
247 	if (col < max_col_reached)		       /* overstrike */
248 	  {
249 	     attr = MOST_BOLD_COLOR;
250 	     if ((col >= min_col) && (col < max_col))
251 	       {
252 		  cell = cells + (col-min_col);
253 		  if (cell->bytes[0] == '_')
254 		    attr = MOST_ULINE_COLOR;
255 		  else if (ch == '_')
256 		    {
257 		       cell->color = MOST_ULINE_COLOR;
258 		       col++;
259 		       continue;
260 		    }
261 	       }
262 	     /* drop */
263 	  }
264 
265 	if (IS_BYTE_PRINTABLE(ch))
266 	  {
267 	     if ((col >= min_col) && (col < max_col))
268 	       {
269 		  cell = cells + (col-min_col);
270 		  cell->bytes = pch;
271 		  cell->len = 1;
272 		  cell->color = attr;
273 		  if (cell >= max_cell)
274 		    max_cell = cell + 1;
275 	       }
276 	     col++;
277 	     prev_width = 1;
278 	     continue;
279 	  }
280 
281 	if ((ch == '\t') && (Most_T_Opt == 0) && (Most_Tab_Width))
282 	  {
283 	     int nspaces = Most_Tab_Width * (col/Most_Tab_Width + 1) - col;
284 	     prev_width = nspaces;
285 	     while (nspaces > 0)
286 	       {
287 		  if ((col >= min_col) && (col < max_col))
288 		    {
289 		       cell = cells + (col-min_col);
290 		       cell->bytes = &cell->byte;
291 		       cell->byte = ' ';
292 		       cell->color = attr;
293 		       cell->len = 1;
294 		       if (cell >= max_cell)
295 			 max_cell = cell + 1;
296 		    }
297 		  col++;
298 		  nspaces--;
299 	       }
300 	     continue;
301 	  }
302 #if 1
303 	if ((ch == 033) && (Most_V_Opt == 0))
304 	  {
305 	     int color;
306 	     if (0 == most_parse_color_escape (&beg, end, &color))
307 	       {
308 		  if (color != -1) default_attr = color;
309 		  continue;
310 	       }
311 	     /* drop */
312 	  }
313 #endif
314 
315 	if (ch & 0x80)
316 	  {
317 	     SLwchar_Type wch;
318 	     if ((Most_UTF8_Mode)
319 		 && (NULL != SLutf8_decode (pch, end, &wch, NULL)))
320 	       {
321 		  int width = SLwchar_wcwidth (wch);
322 		  beg = SLutf8_skip_chars (pch, end, 1, NULL, 1);
323 
324 		  prev_width = width;
325 		  if (width == 0)
326 		    {
327 		       col--;
328 		       if ((col >= min_col) && (col < max_col))
329 			 {
330 			    cell = cells + (col-min_col);
331 			    cell->len += beg-pch;
332 			 }
333 		       col++;
334 		       continue;
335 		    }
336 
337 		  if ((col >= min_col) && (col < max_col))
338 		    {
339 		       cell = cells + (col-min_col);
340 		       cell->bytes = pch;
341 		       cell->color = attr;
342 		       cell->len = beg - pch;
343 		       if (cell >= max_cell)
344 			 max_cell = cell + 1;
345 		    }
346 		  col++;
347 		  if (width > 1)
348 		    {
349 		       if ((col >= min_col) && (col < max_col))
350 			 {
351 			    cell = cells + (col-min_col);
352 			    cell->bytes = pch;
353 			    cell->color = attr;
354 			    cell->len = 0;
355 			    if (cell >= max_cell)
356 			      max_cell = cell + 1;
357 			 }
358 		       col++;
359 		    }
360 		  continue;
361 	       }
362 
363 	     /* Otherwise, this displays as <XX> and takes up 4 character cells */
364 	     sprintf (buf, "<%02X>", (unsigned int) ch);
365 	     prev_width = 4;
366 	     /* drop */
367 	  }
368 	else
369 	  {
370 	     /* Otherwise we have a Ctrl-char displayed as ^X */
371 	     if (ch == 0x7F) ch = '?';
372 	     else ch += '@';
373 
374 	     sprintf (buf, "^%c", ch);
375 	     prev_width = 2;
376 	  }
377 
378 	pch = (unsigned char *)buf;
379 	while (*pch)
380 	  {
381 	     if ((col >= min_col) && (col < max_col))
382 	       {
383 		  cell = cells + (col-min_col);
384 		  cell->bytes = &cell->byte;
385 		  cell->byte = *pch;
386 		  cell->color = attr;
387 		  cell->len = 1;
388 		  if (cell >= max_cell)
389 		    max_cell = cell + 1;
390 	       }
391 	     col++;
392 	     pch++;
393 	  }
394      }
395 
396    if (col < max_col_reached)
397      col = max_col_reached;
398    else
399      max_col_reached = col;
400 
401    /* Now add "..." if selective display.  To do that, the next line needs to
402     * be dealt with to determine whether or not it will be hidden.
403     */
404    if (Most_Selective_Display
405        && (Most_W_Opt == 0)
406        && (beg < Most_Eob)
407        && ((col >= min_col) && (col < max_col)))
408      {
409 	if (*beg == '\n') beg++;
410 
411 	while ((beg < Most_Eob)
412 	       && ((*beg == ' ') || (*beg == '\t') || (*beg == '\r')))
413 	  beg++;
414 
415 	if ((beg >= Most_Eob) || (*beg == '\n')
416 	    || (most_apparant_distance(beg) >= Most_Selective_Display))
417 	  {
418 	     max_col_reached = col + 3;
419 	     while (col < max_col_reached)
420 	       {
421 		  if (col < max_col)
422 		    {
423 		       cell = cells + (col-min_col);
424 		       cell->bytes = &cell->byte;
425 		       cell->byte = '.';
426 		       cell->color = 0;
427 		       cell->len = 1;
428 		       if (cell >= max_cell)
429 			 max_cell = cell + 1;
430 		    }
431 		  col++;
432 	       }
433 	  }
434      }
435    *start_colorp = default_attr;
436    return max_cell - cells;
437 }
438 
display_cells(Multibyte_Cell_Type * cell,unsigned int n,char dollar)439 static void display_cells (Multibyte_Cell_Type *cell, unsigned int n, char dollar)
440 {
441    Multibyte_Cell_Type *cell_max;
442    int last_color = -1;
443 
444    cell_max = cell + n;
445    while (cell < cell_max)
446      {
447 	if (last_color != cell->color)
448 	  {
449 	     last_color = cell->color;
450 	     SLsmg_set_color (last_color);
451 	  }
452 	SLsmg_write_chars (cell->bytes, cell->bytes + cell->len);
453 	cell++;
454      }
455 
456    if (last_color != 0)
457      SLsmg_set_color (0);
458 
459    SLsmg_erase_eol ();
460    if (dollar)
461      {
462 	SLsmg_gotorc (SLsmg_get_row (), SLtt_Screen_Cols-1);
463 	SLsmg_write_nchars (&dollar, 1);
464      }
465 }
466 
most_display_line(int reset)467 void most_display_line (int reset)
468 {
469    unsigned char *beg, *end;
470    unsigned char dollar;
471    static Multibyte_Cell_Type *cells;
472    static unsigned int num_cells;
473    unsigned int screen_cols;
474    unsigned int num_cells_set;
475    static int last_color = 0;	       /* used for a line that wrapped */
476 
477    if (Most_B_Opt)
478      {
479 	output_binary_formatted_line ();
480 	return;
481      }
482 
483    screen_cols = SLtt_Screen_Cols;
484    if (num_cells != screen_cols + 1)
485      {
486 	num_cells = screen_cols + 1;
487 
488 	SLfree ((char *) cells);
489 	if (NULL == (cells = (Multibyte_Cell_Type *)SLcalloc (num_cells, sizeof (Multibyte_Cell_Type))))
490 	  most_exit_error ("Out of memory");
491      }
492 
493    (void) most_extract_line (&beg, &end);
494 
495    if (reset || (Most_W_Opt == 0))
496      last_color = 0;
497    num_cells_set = most_analyse_line (beg, end, cells, num_cells, &last_color);
498 
499    dollar = 0;
500    if (Most_W_Opt)
501      {
502 	if (Most_Show_Wrap_Marker
503 	    && (end < Most_Eob)
504 	    && (*end != '\n'))
505 	  dollar = '\\';
506      }
507    else if (num_cells_set > screen_cols)
508      dollar = '$';
509 
510    display_cells (cells, num_cells_set, dollar);
511 }
512 
513 /* given a position in a line, return apparent distance from bol
514    expanding tabs, etc... up to pos */
most_apparant_distance(unsigned char * pos)515 int most_apparant_distance (unsigned char *pos)
516 {
517    int i, prev_width;
518    unsigned char *save_pos, ch;
519    unsigned int save_offset;
520 
521    save_offset = Most_C_Offset;
522    save_pos = pos;
523    Most_C_Offset = (unsigned int) (pos - Most_Beg);
524    pos = most_beg_of_line();
525    Most_C_Offset = save_offset;
526 
527    i = 0;
528    prev_width = 1;
529    while (pos < save_pos)
530      {
531 	ch = *pos++;
532 	if (IS_BYTE_PRINTABLE(ch))
533 	  {
534 	     i++;
535 	     prev_width = 1;
536 	     continue;
537 	  }
538 
539 	if ((ch == '\b') && (Most_V_Opt == 0))
540 	  {
541 	     i -= prev_width;
542 	     if (i < 0) i = 0;
543 	     continue;
544 	  }
545 	if ((ch == '\r') && (Most_V_Opt == 0))
546 	  {
547 	     if (i != 1) i = 0;
548 	     prev_width = 1;
549 	     continue;
550 	  }
551 	if ((ch == '\t') && (Most_T_Opt == 0))
552 	  {
553 	     prev_width = Most_Tab_Width * (i/Most_Tab_Width + 1) - i;  /* Most_Tab_Width column tabs */
554 	     i += prev_width;
555 	     continue;
556 	  }
557 
558 	if ((ch == 033) && (Most_V_Opt == 0)
559 	    && (0 == most_parse_color_escape (&pos, save_pos, NULL)))
560 	  continue;
561 
562 	if (ch & 0x80)
563 	  {
564 	     SLwchar_Type wch;
565 	     if ((Most_UTF8_Mode)
566 		 && (NULL != SLutf8_decode (pos-1, save_pos, &wch, NULL)))
567 	       {
568 		  prev_width = SLwchar_wcwidth (wch);
569 		  pos = SLutf8_skip_chars (pos-1, save_pos, 1, NULL, 1);
570 		  i += prev_width;
571 		  continue;
572 	       }
573 	     prev_width = 4;
574 	     i += prev_width;	       /* <XX> */
575 	     continue;
576 	  }
577 
578 	prev_width = 2;
579 	i += prev_width;	       /* ^X */
580      }
581    return i;
582 }
583 
584 /*
585  * Returns a pointer to the num_cols'th character after the one
586  * pointed at b. Invisible character runs are not counted toward this
587  * limit, i.e. strings that represent attributes, such as "_\b" for
588  * underlines.
589  *
590  * If multi_column is non-zero, characters spanning more than one
591  * column will add their extra width to the column count.
592  *
593  * If there the end of the buffer is reached, as delimited by argument
594  * e, then e is returned.
595  */
most_forward_columns(unsigned char * b,unsigned char * e,unsigned int num_cols)596 unsigned char *most_forward_columns (unsigned char *b, unsigned char *e, unsigned int num_cols)
597 {
598    unsigned int col = 0;
599    unsigned int prev_width = 1;
600 
601    while (b < e)
602      {
603 	unsigned char ch = *b++;
604 
605 	if (col >=num_cols)
606 	  {
607 	     if ((ch == 033) && (Most_V_Opt == 0))
608 	       {
609 		  while ((ch == 033)
610 			 && (0 == most_parse_color_escape (&b, e, NULL))
611 			 && (b < e))
612 		    ch = *b++;
613 	       }
614 	     b--;
615 	     break;
616 	  }
617 
618 	if (IS_BYTE_PRINTABLE(ch))
619 	  {
620 	     col++;
621 	     prev_width = 1;
622 	     continue;
623 	  }
624 
625 	if (ch & 0x80)
626 	  {
627 	     SLwchar_Type wch;
628 
629 	     if ((Most_UTF8_Mode)
630 		 && (NULL != SLutf8_decode (b-1, e, &wch, NULL)))
631 	       {
632 		  b = SLutf8_skip_chars (b-1, e, 1, NULL, 1);
633 		  prev_width = SLwchar_wcwidth (wch);
634 		  col += prev_width;
635 		  continue;
636 	       }
637 	     prev_width = 4;
638 	     col += prev_width;	       /* <XX> */
639 	     continue;
640 	  }
641 
642 	if (ch == '\b')
643 	  {
644 	     if (Most_V_Opt == 0)
645 	       {
646 		  if (col < prev_width)
647 		    col = 0;
648 		  else
649 		    col -= prev_width;
650 	       }
651 	     else col += 2;	       /* ^H */
652 	     continue;
653 	  }
654 
655 	if (ch == '\r')
656 	  {
657 	     if (Most_V_Opt == 0)
658 	       {
659 		  prev_width = 1;
660 		  col = 0;
661 	       }
662 	     else col += 2;	       /* ^M */
663 	     continue;
664 	  }
665 
666 	if (ch == '\t')
667 	  {
668 	     if (Most_T_Opt == 0)
669 	       {
670 		  prev_width = Most_Tab_Width * (col/Most_Tab_Width + 1) - col;
671 		  col += prev_width;
672 	       }
673 	     else
674 	       col += 2;	       /* ^I */
675 	     continue;
676 	  }
677 
678 	if ((ch == 033) && (Most_V_Opt == 0)
679 	    && (0 == most_parse_color_escape (&b, e, NULL)))
680 	  continue;
681 
682 	/* Ctrl-char ^X */
683 	prev_width = 2;
684 	col += prev_width;
685      }
686    return b;
687 }
688