Added all of the MH sources, including RCS files, in
[mmh] / docs / historical / mh-6.8.5 / miscellany / less-177 / line.c
1 /*
2  * Routines to manipulate the "line buffer".
3  * The line buffer holds a line of output as it is being built
4  * in preparation for output to the screen.
5  */
6
7 #include "less.h"
8
9 static char linebuf[1024];      /* Buffer which holds the current output line */
10 static char attr[1024];         /* Extension of linebuf to hold attributes */
11 static int curr;                /* Index into linebuf */
12 static int column;              /* Printable length, accounting for
13                                    backspaces, etc. */
14 static int overstrike;          /* Next char should overstrike previous char */
15 static int is_null_line;        /* There is no current line */
16 static char pendc;
17
18 static int do_append();
19
20 extern int bs_mode;
21 extern int tabstop;
22 extern int linenums;
23 extern int ctldisp;
24 extern int twiddle;
25 extern int binattr;
26 extern int auto_wrap, ignaw;
27 extern int bo_s_width, bo_e_width;
28 extern int ul_s_width, ul_e_width;
29 extern int bl_s_width, bl_e_width;
30 extern int sc_width, sc_height;
31
32 /*
33  * Rewind the line buffer.
34  */
35         public void
36 prewind()
37 {
38         curr = 0;
39         column = 0;
40         overstrike = 0;
41         is_null_line = 0;
42         pendc = '\0';
43 }
44
45 /*
46  * Insert the line number (of the given position) into the line buffer.
47  */
48         public void
49 plinenum(pos)
50         POSITION pos;
51 {
52         register int lno;
53         register int i;
54         register int n;
55
56         /*
57          * We display the line number at the start of each line
58          * only if the -N option is set.
59          */
60         if (linenums != 2)
61                 return;
62
63         /*
64          * Get the line number and put it in the current line.
65          * {{ Note: since find_linenum calls forw_raw_line,
66          *    it may seek in the input file, requiring the caller 
67          *    of plinenum to re-seek if necessary. }}
68          */
69         lno = find_linenum(pos);
70
71         sprintf(&linebuf[curr], "%6d", lno);
72         n = strlen(&linebuf[curr]);
73         column += n;
74         for (i = 0;  i < n;  i++)
75                 attr[curr++] = 0;
76
77         /*
78          * Append enough spaces to bring us to the next tab stop.
79          * {{ We could avoid this at the cost of adding some
80          *    complication to the tab stop logic in pappend(). }}
81          */
82         do
83         {
84                 linebuf[curr] = ' ';
85                 attr[curr++] = 0;
86                 column++;
87         } while ((column % tabstop) != 0);
88 }
89
90 /*
91  * Return the printing width of the start (enter) sequence
92  * for a given character attribute.
93  */
94         int
95 attr_swidth(a)
96         int a;
97 {
98         switch (a)
99         {
100         case BOLD:      return (bo_s_width);
101         case UNDERLINE: return (ul_s_width);
102         case BLINK:     return (bl_s_width);
103         }
104         return (0);
105 }
106
107 /*
108  * Return the printing width of the end (exit) sequence
109  * for a given character attribute.
110  */
111         int
112 attr_ewidth(a)
113         int a;
114 {
115         switch (a)
116         {
117         case BOLD:      return (bo_e_width);
118         case UNDERLINE: return (ul_e_width);
119         case BLINK:     return (bl_e_width);
120         }
121         return (0);
122 }
123
124 /*
125  * Return the printing width of a given character and attribute,
126  * if the character were added to the current position in the line buffer.
127  * Adding a character with a given attribute may cause an enter or exit
128  * attribute sequence to be inserted, so this must be taken into account.
129  */
130         static int
131 pwidth(c, a)
132         int c;
133         int a;
134 {
135         register int w;
136
137         if (c == '\b')
138                 /*
139                  * Backspace moves backwards one position.
140                  */
141                 return (-1);
142
143         if (control_char(c))
144                 /*
145                  * Control characters do unpredicatable things,
146                  * so we don't even try to guess; say it doesn't move.
147                  * This can only happen if the -r flag is in effect.
148                  */
149                 return (0);
150
151         /*
152          * Other characters take one space,
153          * plus the width of any attribute enter/exit sequence.
154          */
155         w = 1;
156         if (curr > 0 && attr[curr-1] != a)
157                 w += attr_ewidth(attr[curr-1]);
158         if (a && (curr == 0 || attr[curr-1] != a))
159                 w += attr_swidth(a);
160         return (w);
161 }
162
163 /*
164  * Delete the previous character in the line buffer.
165  */
166         static void
167 backc()
168 {
169         curr--;
170         column -= pwidth(linebuf[curr], attr[curr]);
171 }
172
173 /*
174  * Append a character and attribute to the line buffer.
175  */
176         static int
177 storec(c, a)
178         int c;
179         int a;
180 {
181         register int w;
182
183         w = pwidth(c, a);
184         if (ctldisp > 0 && column + w + attr_ewidth(a) > sc_width)
185                 /*
186                  * Won't fit on screen.
187                  */
188                 return (1);
189
190         if (curr >= sizeof(linebuf)-2)
191                 /*
192                  * Won't fit in line buffer.
193                  */
194                 return (1);
195
196         /*
197          * Special handling for "magic cookie" terminals.
198          * If an attribute enter/exit sequence has a printing width > 0,
199          * and the sequence is adjacent to a space, delete the space.
200          * We just mark the space as invisible, to avoid having too
201          * many spaces deleted.
202          * {{ Note that even if the attribute width is > 1, we
203          *    delete only one space.  It's not worth trying to do more.
204          *    It's hardly worth doing this much. }}
205          */
206         if (curr > 0 && a != NORMAL && 
207                 linebuf[curr-1] == ' ' && attr[curr-1] == NORMAL &&
208                 attr_swidth(a) > 0)
209         {
210                 /*
211                  * We are about to append an enter-attribute sequence
212                  * just after a space.  Delete the space.
213                  */
214                 attr[curr-1] = INVIS;
215                 column--;
216         } else if (curr > 0 && attr[curr-1] != NORMAL && 
217                 attr[curr-1] != INVIS && c == ' ' && a == NORMAL &&
218                 attr_ewidth(attr[curr-1]) > 0)
219         {
220                 /*
221                  * We are about to append a space just after an 
222                  * exit-attribute sequence.  Delete the space.
223                  */
224                 a = INVIS;
225                 column--;
226         }
227         /* End of magic cookie handling. */
228
229         linebuf[curr] = c;
230         attr[curr] = a;
231         column += w;
232         return (0);
233 }
234
235 /*
236  * Append a character to the line buffer.
237  * Expand tabs into spaces, handle underlining, boldfacing, etc.
238  * Returns 0 if ok, 1 if couldn't fit in buffer.
239  */
240         public int
241 pappend(c)
242         register int c;
243 {
244         if (pendc)
245         {
246                 if (do_append(pendc))
247                         /*
248                          * Oops.  We've probably lost the char which
249                          * was in pendc, since caller won't back up.
250                          */
251                         return (1);
252                 pendc = '\0';
253         }
254
255         if (c == '\r' && bs_mode == BS_SPECIAL)
256         {
257                 /*
258                  * Don't put the CR into the buffer until we see 
259                  * the next char.  If the next char is a newline,
260                  * discard the CR.
261                  */
262                 pendc = c;
263                 return (0);
264         }
265
266         return (do_append(c));
267 }
268
269         static int
270 do_append(c)
271         int c;
272 {
273         register char *s;
274         register int a;
275
276 #define STOREC(c,a)     if (storec((c),(a))) return (1); else curr++
277
278         if (overstrike)
279         {
280                 /*
281                  * Overstrike the character at the current position
282                  * in the line buffer.  This will cause either 
283                  * underline (if a "_" is overstruck), 
284                  * bold (if an identical character is overstruck),
285                  * or just deletion of the character in the buffer.
286                  */
287                 overstrike = 0;
288                 if (c == linebuf[curr])
289                         STOREC(linebuf[curr], BOLD);
290                 else if (c == '_')
291                         STOREC(linebuf[curr], UNDERLINE);
292                 else if (linebuf[curr] == '_')
293                         STOREC(c, UNDERLINE);
294                 else if (control_char(c))
295                         goto do_control_char;
296                 else
297                         STOREC(c, NORMAL);
298         } else if (c == '\b')
299         {
300                 switch (bs_mode)
301                 {
302                 case BS_NORMAL:
303                         STOREC(c, NORMAL);
304                         break;
305                 case BS_CONTROL:
306                         goto do_control_char;
307                 case BS_SPECIAL:
308                         if (curr == 0)
309                                 break;
310                         backc();
311                         overstrike = 1;
312                         break;
313                 }
314         } else if (c == '\t') 
315         {
316                 /*
317                  * Expand a tab into spaces.
318                  */
319                 do
320                 {
321                         STOREC(' ', NORMAL);
322                 } while ((column % tabstop) != 0);
323         } else if (control_char(c))
324         {
325         do_control_char:
326                 if (ctldisp == 0)
327                 {
328                         /*
329                          * Output as a normal character.
330                          */
331                         STOREC(c, NORMAL);
332                 } else 
333                 {
334                         /*
335                          * Output in the (blinking) ^X format.
336                          */
337                         s = prchar(c);  
338                         a = binattr;
339
340                         /*
341                          * Make sure we can get the entire representation
342                          * the character on this line.
343                          */
344                         if (column + strlen(s) + 
345                             attr_swidth(a) + attr_ewidth(a) > sc_width)
346                                 return (1);
347
348                         for ( ;  *s != 0;  s++)
349                                 STOREC(*s, a);
350                 }
351         } else
352         {
353                 STOREC(c, NORMAL);
354         }
355
356         return (0);
357 }
358
359 /*
360  * Terminate the line in the line buffer.
361  */
362         public void
363 pdone(endline)
364         int endline;
365 {
366         if (pendc && (pendc != '\r' || !endline))
367                 /*
368                  * If we had a pending character, put it in the buffer.
369                  * But discard a pending CR if we are at end of line
370                  * (that is, discard the CR in a CR/LF sequence).
371                  */
372                 (void) do_append(pendc);
373
374         /*
375          * Add a newline if necessary,
376          * and append a '\0' to the end of the line.
377          */
378         if (column < sc_width || !auto_wrap || ignaw || ctldisp == 0)
379         {
380                 linebuf[curr] = '\n';
381                 attr[curr] = NORMAL;
382                 curr++;
383         }
384         linebuf[curr] = '\0';
385         attr[curr] = NORMAL;
386 }
387
388 /*
389  * Get a character from the current line.
390  * Return the character as the function return value,
391  * and the character attribute in *ap.
392  */
393         public int
394 gline(i, ap)
395         register int i;
396         register int *ap;
397 {
398         if (is_null_line)
399         {
400                 /*
401                  * If there is no current line, we pretend the line is
402                  * either "~" or "", depending on the "twiddle" flag.
403                  */
404                 *ap = NORMAL;
405                 if (twiddle)
406                         return ("~\n"[i]);
407                 return ("\n"[i]);
408         }
409
410         *ap = attr[i];
411         return (linebuf[i] & 0377);
412 }
413
414 /*
415  * Indicate that there is no current line.
416  */
417         public void
418 null_line()
419 {
420         is_null_line = 1;
421 }
422
423 /*
424  * Analogous to forw_line(), but deals with "raw lines":
425  * lines which are not split for screen width.
426  * {{ This is supposed to be more efficient than forw_line(). }}
427  */
428         public POSITION
429 forw_raw_line(curr_pos, linep)
430         POSITION curr_pos;
431         char **linep;
432 {
433         register char *p;
434         register int c;
435         POSITION new_pos;
436
437         if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
438                 (c = ch_forw_get()) == EOI)
439                 return (NULL_POSITION);
440
441         p = linebuf;
442
443         for (;;)
444         {
445                 if (c == '\n' || c == EOI)
446                 {
447                         new_pos = ch_tell();
448                         break;
449                 }
450                 if (p >= &linebuf[sizeof(linebuf)-1])
451                 {
452                         /*
453                          * Overflowed the input buffer.
454                          * Pretend the line ended here.
455                          * {{ The line buffer is supposed to be big
456                          *    enough that this never happens. }}
457                          */
458                         new_pos = ch_tell() - 1;
459                         break;
460                 }
461                 *p++ = c;
462                 c = ch_forw_get();
463         }
464         *p = '\0';
465         if (linep != NULL)
466                 *linep = linebuf;
467         return (new_pos);
468 }
469
470 /*
471  * Analogous to back_line(), but deals with "raw lines".
472  * {{ This is supposed to be more efficient than back_line(). }}
473  */
474         public POSITION
475 back_raw_line(curr_pos, linep)
476         POSITION curr_pos;
477         char **linep;
478 {
479         register char *p;
480         register int c;
481         POSITION new_pos;
482
483         if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
484                 ch_seek(curr_pos-1))
485                 return (NULL_POSITION);
486
487         p = &linebuf[sizeof(linebuf)];
488         *--p = '\0';
489
490         for (;;)
491         {
492                 c = ch_back_get();
493                 if (c == '\n')
494                 {
495                         /*
496                          * This is the newline ending the previous line.
497                          * We have hit the beginning of the line.
498                          */
499                         new_pos = ch_tell() + 1;
500                         break;
501                 }
502                 if (c == EOI)
503                 {
504                         /*
505                          * We have hit the beginning of the file.
506                          * This must be the first line in the file.
507                          * This must, of course, be the beginning of the line.
508                          */
509                         new_pos = ch_zero();
510                         break;
511                 }
512                 if (p <= linebuf)
513                 {
514                         /*
515                          * Overflowed the input buffer.
516                          * Pretend the line ended here.
517                          */
518                         new_pos = ch_tell() + 1;
519                         break;
520                 }
521                 *--p = c;
522         }
523         if (linep != NULL)
524                 *linep = p;
525         return (new_pos);
526 }