Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/line.c
106456 views
1
/*
2
* Copyright (C) 1984-2025 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, see the README file.
8
*/
9
10
/*
11
* Routines to manipulate the "line buffer".
12
* The line buffer holds a line of output as it is being built
13
* in preparation for output to the screen.
14
*/
15
16
#include "less.h"
17
#include "charset.h"
18
#include "position.h"
19
20
#if MSDOS_COMPILER==WIN32C
21
#define WIN32_LEAN_AND_MEAN
22
#include <windows.h>
23
#endif
24
25
#define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1)
26
static struct {
27
char *buf; /* Buffer which holds the current output line */
28
int *attr; /* Parallel to buf, to hold attributes */
29
size_t print; /* Index in buf of first printable char */
30
size_t end; /* Number of chars in buf */
31
size_t prev_end; /* Number of chars in buf for previous line */
32
char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */
33
int pfx_attr[MAX_PFX_WIDTH];
34
size_t pfx_end; /* Number of chars in pfx */
35
} linebuf;
36
37
/*
38
* Buffer of ansi sequences which have been shifted off the left edge
39
* of the screen.
40
*/
41
static struct xbuffer shifted_ansi;
42
43
/*
44
* Ring buffer of last ansi sequences sent.
45
* While sending a line, these will be resent at the end
46
* of any highlighted string, to restore text modes.
47
* {{ Not ideal, since we don't really know how many to resend. }}
48
*/
49
#define NUM_LAST_ANSIS 3
50
static struct xbuffer last_ansi;
51
static struct xbuffer last_ansis[NUM_LAST_ANSIS];
52
static int curr_last_ansi;
53
54
static size_t size_linebuf = 0; /* Size of line buffer (and attr buffer) */
55
static struct ansi_state *line_ansi = NULL;
56
static lbool ansi_in_line;
57
static int ff_starts_line;
58
static lbool hlink_in_line;
59
static int line_mark_attr;
60
static int cshift; /* Current left-shift of output line buffer */
61
public int hshift; /* Desired left-shift of output line buffer */
62
public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
63
public int ntabstops = 1; /* Number of tabstops */
64
public int tabdefault = 8; /* Default repeated tabstops */
65
public POSITION highest_hilite; /* Pos of last hilite in file found so far */
66
static POSITION line_pos;
67
static POSITION line_contig_pos = NULL_POSITION; /* One after last byte processed */
68
69
static int end_column; /* Printable length, accounting for backspaces, etc. */
70
static int right_curr;
71
static int right_column;
72
static int overstrike; /* Next char should overstrike previous char */
73
static int last_overstrike = AT_NORMAL;
74
static lbool is_null_line; /* There is no current line */
75
static LWCHAR pendc;
76
static POSITION pendpos;
77
static constant char *end_ansi_chars;
78
static constant char *mid_ansi_chars;
79
static constant char *osc_ansi_chars;
80
static int osc_ansi_allow_count;
81
static long *osc_ansi_allow;
82
static lbool in_hilite;
83
static lbool clear_after_line;
84
85
static int attr_swidth(int a);
86
static int attr_ewidth(int a);
87
static int do_append(LWCHAR ch, constant char *rep, POSITION pos);
88
89
extern int sigs;
90
extern int bs_mode;
91
extern int proc_backspace;
92
extern int proc_tab;
93
extern int proc_return;
94
extern int linenums;
95
extern int ctldisp;
96
extern int twiddle;
97
extern int status_col;
98
extern int status_col_width;
99
extern int linenum_width;
100
extern int auto_wrap, defer_wrap;
101
extern int bo_s_width, bo_e_width;
102
extern int ul_s_width, ul_e_width;
103
extern int bl_s_width, bl_e_width;
104
extern int so_s_width, so_e_width;
105
extern int sc_width, sc_height;
106
extern int utf_mode;
107
extern POSITION start_attnpos;
108
extern POSITION end_attnpos;
109
extern LWCHAR rscroll_char;
110
extern int rscroll_attr;
111
extern int use_color;
112
extern int status_line;
113
114
static char mbc_buf[MAX_UTF_CHAR_LEN];
115
static int mbc_buf_len = 0;
116
static int mbc_buf_index = 0;
117
static POSITION mbc_pos;
118
static size_t saved_line_end;
119
static int saved_end_column;
120
121
/* Configurable color map */
122
struct color_map { int attr; char color[12]; };
123
static struct color_map color_map[] = {
124
{ AT_UNDERLINE, "" },
125
{ AT_BOLD, "" },
126
{ AT_BLINK, "" },
127
{ AT_STANDOUT, "" },
128
{ AT_COLOR_ATTN, "Wm" },
129
{ AT_COLOR_BIN, "kR" },
130
{ AT_COLOR_CTRL, "kR" },
131
{ AT_COLOR_ERROR, "kY" },
132
{ AT_COLOR_LINENUM, "c*" },
133
{ AT_COLOR_MARK, "Wb" },
134
{ AT_COLOR_PROMPT, "kC" },
135
{ AT_COLOR_RSCROLL, "kc" },
136
{ AT_COLOR_HEADER, "" },
137
{ AT_COLOR_SEARCH, "kG" },
138
{ AT_COLOR_SUBSEARCH(1), "ky" },
139
{ AT_COLOR_SUBSEARCH(2), "wb" },
140
{ AT_COLOR_SUBSEARCH(3), "YM" },
141
{ AT_COLOR_SUBSEARCH(4), "Yr" },
142
{ AT_COLOR_SUBSEARCH(5), "Wc" },
143
};
144
145
/* State while processing an ANSI escape sequence */
146
struct ansi_state {
147
osc8_state ostate; /* State while processing OSC8 sequence */
148
unsigned int otype; /* OSC type number */
149
unsigned int escs_in_seq;
150
};
151
152
/*
153
* Initialize from environment variables.
154
*/
155
public void init_line(void)
156
{
157
int ax;
158
constant char *s;
159
160
end_ansi_chars = lgetenv("LESSANSIENDCHARS");
161
if (isnullenv(end_ansi_chars))
162
end_ansi_chars = "m";
163
164
mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
165
if (isnullenv(mid_ansi_chars))
166
mid_ansi_chars = "0123456789:;[?!\"'#%()*+ ";
167
168
osc_ansi_chars = lgetenv("LESSANSIOSCCHARS");
169
if (isnullenv(osc_ansi_chars))
170
osc_ansi_chars = "";
171
172
osc_ansi_allow_count = 0;
173
s = lgetenv("LESSANSIOSCALLOW");
174
if (!isnullenv(s))
175
{
176
struct xbuffer xbuf;
177
xbuf_init(&xbuf);
178
for (;;)
179
{
180
long num;
181
s = skipspc(s);
182
if (*s == '\0')
183
break;
184
num = lstrtoulc(s, &s, 10);
185
s = skipspc(s);
186
if (*s == ',')
187
++s;
188
xbuf_add_data(&xbuf, &num, sizeof(num));
189
++osc_ansi_allow_count;
190
}
191
osc_ansi_allow = (long *) xbuf.data;
192
}
193
194
linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
195
linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int));
196
size_linebuf = LINEBUF_SIZE;
197
xbuf_init(&shifted_ansi);
198
xbuf_init(&last_ansi);
199
for (ax = 0; ax < NUM_LAST_ANSIS; ax++)
200
xbuf_init(&last_ansis[ax]);
201
curr_last_ansi = 0;
202
}
203
204
/*
205
* Expand the line buffer.
206
*/
207
static int expand_linebuf(void)
208
{
209
/* Double the size of the line buffer. */
210
size_t new_size = size_linebuf * 2;
211
char *new_buf = (char *) calloc(new_size, sizeof(char));
212
int *new_attr = (int *) calloc(new_size, sizeof(int));
213
if (new_buf == NULL || new_attr == NULL)
214
{
215
if (new_attr != NULL)
216
free(new_attr);
217
if (new_buf != NULL)
218
free(new_buf);
219
return 1;
220
}
221
/*
222
* We just calloc'd the buffers; copy the old contents.
223
*/
224
memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char));
225
memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int));
226
free(linebuf.attr);
227
free(linebuf.buf);
228
linebuf.buf = new_buf;
229
linebuf.attr = new_attr;
230
size_linebuf = new_size;
231
return 0;
232
}
233
234
/*
235
* Is a character ASCII?
236
*/
237
public lbool is_ascii_char(LWCHAR ch)
238
{
239
return (ch <= 0x7F);
240
}
241
242
/*
243
*/
244
static void inc_end_column(int w)
245
{
246
if (end_column > right_column && w > 0)
247
{
248
right_column = end_column;
249
right_curr = (int) linebuf.end;
250
}
251
end_column += w;
252
}
253
254
public POSITION line_position(void)
255
{
256
return line_pos;
257
}
258
259
/*
260
* Is this byte the next one after the previous byte processed?
261
*/
262
public lbool is_line_contig_pos(POSITION pos)
263
{
264
return pos == line_contig_pos;
265
}
266
267
/*
268
* Set the position of the next byte to be processed.
269
*/
270
public void set_line_contig_pos(POSITION pos)
271
{
272
line_contig_pos = pos;
273
}
274
275
/*
276
* Copy any ANSI sequences from line buffer to shifted_ansi.
277
*/
278
static void pshift(size_t end)
279
{
280
size_t i;
281
for (i = linebuf.print; i < end; i++)
282
if (linebuf.attr[i] == AT_ANSI)
283
xbuf_add_char(&shifted_ansi, linebuf.buf[i]);
284
}
285
286
/*
287
* Rewind the line buffer.
288
*/
289
public void prewind(lbool contig)
290
{
291
int ax;
292
293
xbuf_reset(&shifted_ansi);
294
if (contig && linebuf.prev_end != 0)
295
pshift(linebuf.prev_end);
296
linebuf.print = 6; /* big enough for longest UTF-8 sequence */
297
linebuf.pfx_end = 0;
298
for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++)
299
{
300
linebuf.buf[linebuf.end] = '\0';
301
linebuf.attr[linebuf.end] = 0;
302
}
303
304
end_column = 0;
305
right_curr = 0;
306
right_column = 0;
307
cshift = 0;
308
overstrike = 0;
309
last_overstrike = AT_NORMAL;
310
mbc_buf_len = 0;
311
is_null_line = FALSE;
312
pendc = '\0';
313
in_hilite = FALSE;
314
ansi_in_line = FALSE;
315
ff_starts_line = -1;
316
hlink_in_line = FALSE;
317
clear_after_line = FALSE;
318
line_mark_attr = 0;
319
line_pos = NULL_POSITION;
320
xbuf_reset(&last_ansi);
321
for (ax = 0; ax < NUM_LAST_ANSIS; ax++)
322
xbuf_reset(&last_ansis[ax]);
323
curr_last_ansi = 0;
324
}
325
326
/*
327
* Set a character in the line buffer.
328
*/
329
static void set_linebuf(size_t n, char ch, int attr)
330
{
331
if (n >= size_linebuf)
332
{
333
/*
334
* Won't fit in line buffer.
335
* Try to expand it.
336
*/
337
if (expand_linebuf())
338
return;
339
}
340
linebuf.buf[n] = ch;
341
linebuf.attr[n] = attr;
342
}
343
344
/*
345
* Append a character to the line buffer.
346
*/
347
static void add_linebuf(char ch, int attr, int w)
348
{
349
set_linebuf(linebuf.end++, ch, attr);
350
inc_end_column(w);
351
}
352
353
/*
354
* Append a string to the line buffer.
355
*/
356
static void addstr_linebuf(constant char *s, int attr, int cw)
357
{
358
for ( ; *s != '\0'; s++)
359
add_linebuf(*s, attr, cw);
360
}
361
362
/*
363
* Set a character in the line prefix buffer.
364
*/
365
static void set_pfx(size_t n, char ch, int attr)
366
{
367
linebuf.pfx[n] = ch;
368
linebuf.pfx_attr[n] = attr;
369
}
370
371
/*
372
* Append a character to the line prefix buffer.
373
*/
374
static void add_pfx(char ch, int attr)
375
{
376
set_pfx(linebuf.pfx_end++, ch, attr);
377
}
378
379
/*
380
* Insert the status column and line number into the line buffer.
381
*/
382
public void plinestart(POSITION pos)
383
{
384
LINENUM linenum = 0;
385
386
if (linenums == OPT_ONPLUS)
387
{
388
/*
389
* Get the line number and put it in the current line.
390
* {{ Note: since find_linenum calls forw_raw_line,
391
* it may seek in the input file, requiring the caller
392
* of plinestart to re-seek if necessary. }}
393
* {{ Since forw_raw_line modifies linebuf, we must
394
* do this first, before storing anything in linebuf. }}
395
*/
396
linenum = find_linenum(pos);
397
}
398
399
/*
400
* Display a status column if the -J option is set.
401
*/
402
if (status_col || status_line)
403
{
404
char c = posmark(pos);
405
if (c != 0)
406
line_mark_attr = AT_HILITE|AT_COLOR_MARK;
407
else if (start_attnpos != NULL_POSITION &&
408
pos >= start_attnpos && pos <= end_attnpos)
409
line_mark_attr = AT_HILITE|AT_COLOR_ATTN;
410
if (status_col)
411
{
412
add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */
413
while (linebuf.pfx_end < (size_t) status_col_width) /*{{type-issue}}*/
414
add_pfx(' ', AT_NORMAL);
415
}
416
}
417
418
/*
419
* Display the line number at the start of each line
420
* if the -N option is set.
421
*/
422
if (linenums == OPT_ONPLUS)
423
{
424
char buf[INT_STRLEN_BOUND(linenum) + 2];
425
size_t len;
426
size_t i;
427
428
linenum = vlinenum(linenum);
429
if (linenum == 0)
430
len = 0;
431
else
432
{
433
linenumtoa(linenum, buf, 10);
434
len = strlen(buf);
435
}
436
for (i = 0; i + len < (size_t) linenum_width; i++)
437
add_pfx(' ', AT_NORMAL);
438
for (i = 0; i < len; i++)
439
add_pfx(buf[i], use_color ? AT_COLOR_LINENUM : AT_BOLD);
440
add_pfx(' ', AT_NORMAL);
441
}
442
end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/
443
}
444
445
/*
446
* Return the width of the line prefix (status column and line number).
447
* {{ Actual line number can be wider than linenum_width. }}
448
*/
449
public int line_pfx_width(void)
450
{
451
int width = 0;
452
if (status_col)
453
width += status_col_width;
454
if (linenums == OPT_ONPLUS)
455
width += linenum_width + 1;
456
return width;
457
}
458
459
/*
460
* Shift line left so that the last char is just to the left
461
* of the first visible column.
462
*/
463
public void pshift_all(void)
464
{
465
pshift(linebuf.end);
466
linebuf.end = linebuf.print;
467
end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/
468
line_pos = NULL_POSITION;
469
}
470
471
/*
472
* Return the printing width of the start (enter) sequence
473
* for a given character attribute.
474
*/
475
static int attr_swidth(int a)
476
{
477
int w = 0;
478
479
a = apply_at_specials(a);
480
481
if (a & AT_UNDERLINE)
482
w += ul_s_width;
483
if (a & AT_BOLD)
484
w += bo_s_width;
485
if (a & AT_BLINK)
486
w += bl_s_width;
487
if (a & AT_STANDOUT)
488
w += so_s_width;
489
490
return w;
491
}
492
493
/*
494
* Return the printing width of the end (exit) sequence
495
* for a given character attribute.
496
*/
497
static int attr_ewidth(int a)
498
{
499
int w = 0;
500
501
a = apply_at_specials(a);
502
503
if (a & AT_UNDERLINE)
504
w += ul_e_width;
505
if (a & AT_BOLD)
506
w += bo_e_width;
507
if (a & AT_BLINK)
508
w += bl_e_width;
509
if (a & AT_STANDOUT)
510
w += so_e_width;
511
512
return w;
513
}
514
515
/*
516
* Return the printing width of a given character and attribute,
517
* if the character were added after prev_ch.
518
* Adding a character with a given attribute may cause an enter or exit
519
* attribute sequence to be inserted, so this must be taken into account.
520
*/
521
public int pwidth(LWCHAR ch, int a, LWCHAR prev_ch, int prev_a)
522
{
523
int w;
524
525
if (ch == '\b')
526
{
527
/*
528
* Backspace moves backwards one or two positions.
529
*/
530
if (prev_a & (AT_ANSI|AT_BINARY))
531
return (int) strlen(prchar('\b')); /*{{type-issue}}*/
532
return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
533
}
534
535
if (!utf_mode || is_ascii_char(ch))
536
{
537
if (control_char(ch))
538
{
539
/*
540
* Control characters do unpredictable things,
541
* so we don't even try to guess; say it doesn't move.
542
* This can only happen if the -r flag is in effect.
543
*/
544
return (0);
545
}
546
} else
547
{
548
if (ch == VARSEL_15)
549
/* If prev char was double width, make it single width. */
550
return (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 2) ? -1 : 0;
551
if (ch == VARSEL_16)
552
/* If prev char was single width, make it double width. */
553
return (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 1) ? +1 : 0;
554
if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
555
{
556
/*
557
* Composing and combining chars take up no space.
558
*
559
* Some terminals, upon failure to compose a
560
* composing character with the character(s) that
561
* precede(s) it will actually take up one end_column
562
* for the composing character; there isn't much
563
* we could do short of testing the (complex)
564
* composition process ourselves and printing
565
* a binary representation when it fails.
566
*/
567
return (0);
568
}
569
}
570
571
/*
572
* Other characters take one or two columns,
573
* plus the width of any attribute enter/exit sequence.
574
*/
575
w = 1;
576
if (is_wide_char(ch))
577
w++;
578
if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a))
579
w += attr_ewidth(linebuf.attr[linebuf.end-1]);
580
if (apply_at_specials(a) != AT_NORMAL &&
581
(linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a)))
582
w += attr_swidth(a);
583
return (w);
584
}
585
586
/*
587
* Delete to the previous base character in the line buffer.
588
*/
589
static int backc(void)
590
{
591
LWCHAR ch;
592
char *p;
593
594
if (linebuf.end == 0)
595
return (0);
596
p = &linebuf.buf[linebuf.end];
597
ch = step_char(&p, -1, linebuf.buf);
598
/* Skip back to the next nonzero-width char. */
599
while (p > linebuf.buf)
600
{
601
LWCHAR prev_ch;
602
int width;
603
linebuf.end = ptr_diff(p, linebuf.buf);
604
prev_ch = step_char(&p, -1, linebuf.buf);
605
width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]);
606
end_column -= width;
607
/* {{ right_column? }} */
608
if (width > 0)
609
break;
610
ch = prev_ch;
611
}
612
return (1);
613
}
614
615
/*
616
* Preserve the current position in the line buffer (for word wrapping).
617
*/
618
public void savec(void)
619
{
620
saved_line_end = linebuf.end;
621
saved_end_column = end_column;
622
}
623
624
/*
625
* Restore the position in the line buffer (start of line for word wrapping).
626
*/
627
public void loadc(void)
628
{
629
linebuf.end = saved_line_end;
630
end_column = saved_end_column;
631
}
632
633
/*
634
* Is a character the end of an ANSI escape sequence?
635
*/
636
public lbool is_ansi_end(LWCHAR ch)
637
{
638
if (!is_ascii_char(ch))
639
return (FALSE);
640
return (ch != 0 && strchr(end_ansi_chars, (char) ch) != NULL);
641
}
642
643
/*
644
* Can a char appear in an ANSI escape sequence, before the end char?
645
*/
646
public lbool is_ansi_middle(LWCHAR ch)
647
{
648
if (!is_ascii_char(ch))
649
return (FALSE);
650
if (is_ansi_end(ch))
651
return (FALSE);
652
return (ch != 0 && strchr(mid_ansi_chars, (char) ch) != NULL);
653
}
654
655
/*
656
* Skip past an ANSI escape sequence.
657
* pp is initially positioned just after the CSI_START char.
658
*/
659
public void skip_ansi(struct ansi_state *pansi, LWCHAR ch, constant char **pp, constant char *limit)
660
{
661
ansi_step(pansi, ch);
662
do {
663
ch = step_charc(pp, +1, limit);
664
} while (*pp < limit && ansi_step(pansi, ch) == ANSI_MID);
665
/* Note that we discard final char, for which is_ansi_end is true. */
666
}
667
668
/*
669
* Determine if a character starts an ANSI escape sequence.
670
* If so, return an ansi_state struct; otherwise return NULL.
671
*/
672
public struct ansi_state * ansi_start(LWCHAR ch)
673
{
674
struct ansi_state *pansi;
675
676
if (!IS_CSI_START(ch))
677
return NULL;
678
pansi = ecalloc(1, sizeof(struct ansi_state));
679
pansi->ostate = OSC_START;
680
pansi->otype = 0;
681
pansi->escs_in_seq = 0;
682
return pansi;
683
}
684
685
/*
686
* Is a character a valid intro char for an OSC sequence?
687
* An intro char is the one immediately after the ESC, usually ']'.
688
*/
689
static lbool valid_osc_intro(char ch, lbool content)
690
{
691
constant char *p = strchr(osc_ansi_chars, ch);
692
if (p == NULL)
693
return FALSE;
694
return (!content || p[1] == '*');
695
}
696
697
/*
698
* Is a given number a valid OSC type?
699
*/
700
static lbool valid_osc_type(int otype, lbool content)
701
{
702
int i;
703
if (!content)
704
return TRUE;
705
if (otype == 8)
706
return TRUE;
707
for (i = 0; i < osc_ansi_allow_count; i++)
708
if (osc_ansi_allow[i] == otype)
709
return TRUE;
710
return FALSE;
711
}
712
713
/*
714
* Helper function for ansi_step.
715
*/
716
static ansi_state osc_return(struct ansi_state *pansi, osc8_state ostate, ansi_state astate)
717
{
718
pansi->ostate = ostate;
719
return astate;
720
}
721
722
/*
723
* Determine whether the next char in an ANSI escape sequence
724
* ends the sequence.
725
*/
726
static ansi_state ansi_step2(struct ansi_state *pansi, LWCHAR ch, lbool content)
727
{
728
/*
729
* Pass thru OS commands. Assume OSC commands do not move the cursor.
730
* A "typed" OSC starts with ESC ] <integer> <semicolon>, followed by an
731
* arbitrary string, and ends with a String Terminator (ESC-backslash or BEL).
732
* An untyped OSC starts with ESC ] or ESC x where x is in osc_ansi_chars,
733
* and ends with ST.
734
* The only typed OSC we actually parse is OSC 8.
735
*/
736
switch (pansi->ostate)
737
{
738
case OSC_START:
739
if (IS_CSI_START(ch))
740
return osc_return(pansi, OSC_INTRO, ANSI_MID);
741
break;
742
case OSC_INTRO:
743
if (ch == ']')
744
return osc_return(pansi, OSC_TYPENUM, ANSI_MID);
745
if (is_ascii_char(ch) && valid_osc_intro((char) ch, content))
746
return osc_return(pansi, OSC_STRING, ANSI_MID);
747
if (IS_CSI_START(ch))
748
return osc_return(pansi, OSC_INTRO, ANSI_MID);
749
/* ESC not followed by bracket; restart. */
750
pansi->ostate = OSC_START;
751
break;
752
case OSC_TYPENUM:
753
if (ch >= '0' && ch <= '9')
754
{
755
if (ckd_mul(&pansi->otype, pansi->otype, 10) ||
756
ckd_add(&pansi->otype, pansi->otype, ch - '0'))
757
return osc_return(pansi, OSC_STRING, ANSI_MID);
758
return osc_return(pansi, OSC_TYPENUM, ANSI_MID);
759
}
760
if (ch == ';')
761
return osc_return(pansi, (pansi->otype == 8) ? OSC8_PARAMS : OSC_STRING, ANSI_MID);
762
/* OSC is untyped */
763
if (IS_CSI_START(ch))
764
return osc_return(pansi, OSC_END_CSI, ANSI_MID);
765
if (ch == '\7')
766
return osc_return(pansi, OSC_END, ANSI_END);
767
return osc_return(pansi, OSC_STRING, ANSI_MID);
768
case OSC8_PARAMS:
769
if (ch == ';')
770
return osc_return(pansi, OSC8_URI, ANSI_MID);
771
/* FALLTHRU */
772
case OSC8_URI:
773
case OSC_STRING:
774
/* Look for ST. */
775
if (ch == '\7')
776
return osc_return(pansi, OSC_END, valid_osc_type(pansi->otype, content) ? ANSI_END : ANSI_ERR);
777
if (IS_CSI_START(ch))
778
{
779
pansi->escs_in_seq++;
780
return osc_return(pansi, OSC_END_CSI, ANSI_MID);
781
}
782
/* Stay in same ostate */
783
return ANSI_MID;
784
case OSC_END_CSI:
785
/* Got ESC of ST, expect backslash next. */
786
if (ch == '\\')
787
return osc_return(pansi, OSC_END, valid_osc_type(pansi->otype, content) ? ANSI_END : ANSI_ERR);
788
/* ESC not followed by backslash. */
789
return osc_return(pansi, OSC_STRING, ANSI_MID);
790
case OSC_END:
791
return ANSI_END;
792
case OSC8_NOT:
793
/* cannot happen */
794
break;
795
}
796
/* Check for SGR sequences */
797
if (is_ansi_middle(ch))
798
return ANSI_MID;
799
if (is_ansi_end(ch))
800
return ANSI_END;
801
return ANSI_ERR;
802
}
803
804
public ansi_state ansi_step(struct ansi_state *pansi, LWCHAR ch)
805
{
806
return ansi_step2(pansi, ch, TRUE);
807
}
808
809
/*
810
* Return the current OSC8 parsing state.
811
*/
812
public osc8_state ansi_osc8_state(struct ansi_state *pansi)
813
{
814
return pansi->ostate;
815
}
816
817
/*
818
* Free an ansi_state structure.
819
*/
820
public void ansi_done(struct ansi_state *pansi)
821
{
822
free(pansi);
823
}
824
825
/*
826
* Will w characters in attribute a fit on the screen?
827
*/
828
static lbool fits_on_screen(int w, int a)
829
{
830
if (ctldisp == OPT_ON)
831
/* We're not counting, so say that everything fits. */
832
return TRUE;
833
return (end_column - cshift + w + attr_ewidth(a) <= sc_width);
834
}
835
836
/*
837
* Append a character and attribute to the line buffer.
838
*/
839
#define STORE_CHAR(ch,a,rep,pos) \
840
do { \
841
if (store_char((ch),(a),(rep),(pos))) return (1); \
842
} while (0)
843
844
static int store_char(LWCHAR ch, int a, constant char *rep, POSITION pos)
845
{
846
int w;
847
size_t i;
848
size_t replen;
849
char cs;
850
int ov;
851
lbool need_shift;
852
853
ov = (a & (AT_UNDERLINE|AT_BOLD));
854
if (ov != AT_NORMAL)
855
last_overstrike = ov;
856
857
#if HILITE_SEARCH
858
{
859
int matches;
860
int resend_last = 0;
861
int hl_attr = 0;
862
863
if (pos != NULL_POSITION && a != AT_ANSI)
864
{
865
hl_attr = is_hilited_attr(pos, pos+1, 0, &matches);
866
if (hl_attr == 0 && status_line)
867
hl_attr = line_mark_attr;
868
}
869
if (hl_attr)
870
{
871
/*
872
* This character should be highlighted.
873
* Override the attribute passed in.
874
*/
875
a |= hl_attr;
876
if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite)
877
highest_hilite = pos;
878
in_hilite = TRUE;
879
} else
880
{
881
if (in_hilite)
882
{
883
/*
884
* This is the first non-hilited char after a hilite.
885
* Resend the last ANSI seq to restore color.
886
*/
887
resend_last = 1;
888
}
889
in_hilite = FALSE;
890
}
891
if (resend_last)
892
{
893
int ai;
894
for (ai = 0; ai < NUM_LAST_ANSIS; ai++)
895
{
896
int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS;
897
for (i = 0; i < last_ansis[ax].end; i++)
898
STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos);
899
}
900
}
901
}
902
#endif
903
904
if (a == AT_ANSI) {
905
w = 0;
906
} else {
907
char *p = &linebuf.buf[linebuf.end];
908
LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
909
int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0;
910
w = pwidth(ch, a, prev_ch, prev_a);
911
}
912
913
if (!fits_on_screen(w, a))
914
return (1);
915
916
if (rep == NULL)
917
{
918
cs = (char) ch;
919
rep = &cs;
920
replen = 1;
921
} else
922
{
923
replen = (size_t) utf_len(rep[0]); /*{{type-issue}}*/
924
}
925
926
if (cshift == hshift)
927
{
928
if (line_pos == NULL_POSITION)
929
line_pos = pos;
930
if (shifted_ansi.end > 0)
931
{
932
/* Copy shifted ANSI sequences to beginning of line. */
933
for (i = 0; i < shifted_ansi.end; i++)
934
add_linebuf((char) shifted_ansi.data[i], AT_ANSI, 0);
935
xbuf_reset(&shifted_ansi);
936
}
937
if (linebuf.end == linebuf.print+1)
938
{
939
/* If first char is a placeholder, the one before it is double-width.
940
* VS15 changes the double-width char to single-width, so replace the
941
* placeholder with this VS15. */
942
if (ch == VARSEL_15 && (linebuf.attr[linebuf.end-1] & AT_PLACEHOLDER))
943
{
944
linebuf.end--;
945
inc_end_column(-1);
946
}
947
} else if (linebuf.end == linebuf.print)
948
{
949
/* VS16 changes the previous single-width char to double-width.
950
* Add a placeholder to represent the second half of the
951
* double-width char. */
952
if (ch == VARSEL_16)
953
{
954
char *p = &linebuf.buf[linebuf.end];
955
LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
956
if (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 1)
957
add_linebuf(' ', rscroll_attr|AT_PLACEHOLDER, 0);
958
}
959
}
960
}
961
962
/* Add the char to the buf, even if we will left-shift it next. */
963
need_shift = (cshift < hshift);
964
if (!need_shift && w <= 0 && linebuf.end <= linebuf.print+1 && is_composing_char(ch) &&
965
(linebuf.end == linebuf.print || (linebuf.end == linebuf.print+1 && (linebuf.attr[linebuf.end-1] & AT_PLACEHOLDER))))
966
need_shift = TRUE;
967
inc_end_column(w);
968
for (i = 0; i < replen; i++)
969
add_linebuf(*rep++, a, 0);
970
971
if (need_shift)
972
{
973
/* We haven't left-shifted enough yet. */
974
if (a == AT_ANSI)
975
xbuf_add_char(&shifted_ansi, (char) ch); /* Save ANSI attributes */
976
if (linebuf.end > linebuf.print)
977
{
978
/* Shift left enough to put last byte of this char at print-1. */
979
size_t i;
980
for (i = 0; i < linebuf.print; i++)
981
{
982
linebuf.buf[i] = linebuf.buf[i+replen];
983
linebuf.attr[i] = linebuf.attr[i+replen];
984
}
985
linebuf.end -= replen;
986
cshift += w;
987
/*
988
* If the char we just left-shifted was double width,
989
* the 2 spaces we shifted may be too much.
990
* Represent the "half char" at start of line with a highlighted space.
991
*/
992
while (cshift > hshift)
993
{
994
add_linebuf(' ', rscroll_attr|AT_PLACEHOLDER, 0);
995
cshift--;
996
}
997
}
998
}
999
return (0);
1000
}
1001
1002
#define STORE_STRING(s,a,pos) \
1003
do { if (store_string((s),(a),(pos))) return (1); } while (0)
1004
1005
static int store_string(constant char *s, int a, POSITION pos)
1006
{
1007
if (!fits_on_screen((int) strlen(s), a))
1008
return 1;
1009
for ( ; *s != 0; s++)
1010
STORE_CHAR((LWCHAR)*s, a, NULL, pos);
1011
return 0;
1012
}
1013
1014
/*
1015
* Return number of spaces from col to the next tab stop.
1016
*/
1017
static int tab_spaces(int col)
1018
{
1019
int to_tab = col - (int) linebuf.pfx_end; /*{{type-issue}}*/
1020
1021
if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
1022
to_tab = tabdefault -
1023
((to_tab - tabstops[ntabstops-1]) % tabdefault);
1024
else
1025
{
1026
int i;
1027
for (i = ntabstops - 2; i >= 0; i--)
1028
if (to_tab >= tabstops[i])
1029
break;
1030
to_tab = tabstops[i+1] - to_tab;
1031
}
1032
return to_tab;
1033
}
1034
1035
/*
1036
* Append a tab to the line buffer.
1037
* Store spaces to represent the tab.
1038
*/
1039
#define STORE_TAB(a,pos) \
1040
do { if (store_tab((a),(pos))) return (1); } while (0)
1041
1042
static int store_tab(int attr, POSITION pos)
1043
{
1044
int to_tab = tab_spaces(end_column);
1045
do {
1046
STORE_CHAR(' ', attr, " ", pos);
1047
} while (--to_tab > 0);
1048
return 0;
1049
}
1050
1051
#define STORE_PRCHAR(c, pos) \
1052
do { if (store_prchar((c), (pos))) return 1; } while (0)
1053
1054
static int store_prchar(LWCHAR c, POSITION pos)
1055
{
1056
/*
1057
* Convert to printable representation.
1058
*/
1059
STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos);
1060
return 0;
1061
}
1062
1063
static int flush_mbc_buf(POSITION pos)
1064
{
1065
int i;
1066
1067
for (i = 0; i < mbc_buf_index; i++)
1068
if (store_prchar((LWCHAR) mbc_buf[i], pos))
1069
return mbc_buf_index - i;
1070
return 0;
1071
}
1072
1073
/*
1074
* Append a character to the line buffer.
1075
* Expand tabs into spaces, handle underlining, boldfacing, etc.
1076
* Returns 0 if ok, 1 if couldn't fit in buffer.
1077
*/
1078
public int pappend_b(char c, POSITION pos, lbool before_pendc)
1079
{
1080
LWCHAR ch = c & 0377;
1081
int r;
1082
1083
if (pendc && !before_pendc)
1084
{
1085
if (ch == '\r' && pendc == '\r')
1086
return (0);
1087
if (do_append(pendc, NULL, pendpos))
1088
/*
1089
* Oops. We've probably lost the char which
1090
* was in pendc, since caller won't back up.
1091
*/
1092
return (1);
1093
pendc = '\0';
1094
}
1095
1096
if (ch == '\r' && (proc_return == OPT_ON || (bs_mode == BS_SPECIAL && proc_return == OPT_OFF)))
1097
{
1098
if (mbc_buf_len > 0) /* utf_mode must be on. */
1099
{
1100
/* Flush incomplete (truncated) sequence. */
1101
r = flush_mbc_buf(mbc_pos);
1102
mbc_buf_index = r + 1;
1103
mbc_buf_len = 0;
1104
if (r)
1105
return (mbc_buf_index);
1106
}
1107
1108
/*
1109
* Don't put the CR into the buffer until we see
1110
* the next char. If the next char is a newline,
1111
* discard the CR.
1112
*/
1113
pendc = ch;
1114
pendpos = pos;
1115
return (0);
1116
}
1117
1118
if (!utf_mode)
1119
{
1120
r = do_append(ch, NULL, pos);
1121
} else
1122
{
1123
/* Perform strict validation in all possible cases. */
1124
if (mbc_buf_len == 0)
1125
{
1126
retry:
1127
mbc_buf_index = 1;
1128
*mbc_buf = c;
1129
if (IS_ASCII_OCTET(c))
1130
r = do_append(ch, NULL, pos);
1131
else if (IS_UTF8_LEAD(c))
1132
{
1133
mbc_buf_len = utf_len(c);
1134
mbc_pos = pos;
1135
return (0);
1136
} else
1137
/* UTF8_INVALID or stray UTF8_TRAIL */
1138
r = flush_mbc_buf(pos);
1139
} else if (IS_UTF8_TRAIL(c))
1140
{
1141
mbc_buf[mbc_buf_index++] = c;
1142
if (mbc_buf_index < mbc_buf_len)
1143
return (0);
1144
if (is_utf8_well_formed(mbc_buf, mbc_buf_index))
1145
r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
1146
else
1147
/* Complete, but not shortest form, sequence. */
1148
mbc_buf_index = r = flush_mbc_buf(mbc_pos);
1149
mbc_buf_len = 0;
1150
} else
1151
{
1152
/* Flush incomplete (truncated) sequence. */
1153
r = flush_mbc_buf(mbc_pos);
1154
mbc_buf_index = r + 1;
1155
mbc_buf_len = 0;
1156
/* Handle new char. */
1157
if (!r)
1158
goto retry;
1159
}
1160
}
1161
if (r)
1162
{
1163
/* How many chars should caller back up? */
1164
r = (!utf_mode) ? 1 : mbc_buf_index;
1165
}
1166
return (r);
1167
}
1168
1169
public int pappend(char c, POSITION pos)
1170
{
1171
if (ff_starts_line < 0)
1172
ff_starts_line = (c == CONTROL('L'));
1173
return pappend_b(c, pos, FALSE);
1174
}
1175
1176
public lbool line_is_ff(void)
1177
{
1178
return (ff_starts_line == 1);
1179
}
1180
1181
static int store_control_char(LWCHAR ch, constant char *rep, POSITION pos)
1182
{
1183
if (ctldisp == OPT_ON)
1184
{
1185
/* Output the character itself. */
1186
STORE_CHAR(ch, AT_NORMAL, rep, pos);
1187
} else
1188
{
1189
/* Output a printable representation of the character. */
1190
STORE_PRCHAR(ch, pos);
1191
}
1192
return (0);
1193
}
1194
1195
static int store_ansi(LWCHAR ch, constant char *rep, POSITION pos)
1196
{
1197
switch (ansi_step2(line_ansi, ch, pos != NULL_POSITION))
1198
{
1199
case ANSI_MID:
1200
STORE_CHAR(ch, AT_ANSI, rep, pos);
1201
switch (ansi_osc8_state(line_ansi))
1202
{
1203
case OSC_TYPENUM: case OSC_STRING: hlink_in_line = TRUE; break;
1204
default: break;
1205
}
1206
xbuf_add_char(&last_ansi, (char) ch);
1207
break;
1208
case ANSI_END:
1209
STORE_CHAR(ch, AT_ANSI, rep, pos);
1210
ansi_done(line_ansi);
1211
line_ansi = NULL;
1212
xbuf_add_char(&last_ansi, (char) ch);
1213
xbuf_set(&last_ansis[curr_last_ansi], &last_ansi);
1214
xbuf_reset(&last_ansi);
1215
curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS;
1216
break;
1217
case ANSI_ERR:
1218
{
1219
/* Remove whole unrecognized sequence. */
1220
constant char *start = (cshift < hshift) ? xbuf_char_data(&shifted_ansi): linebuf.buf;
1221
size_t *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end;
1222
constant char *p = start + *end;
1223
LWCHAR bch;
1224
do {
1225
bch = step_charc(&p, -1, start);
1226
} while (p > start && (!IS_CSI_START(bch) || line_ansi->escs_in_seq-- > 0));
1227
*end = ptr_diff(p, start);
1228
}
1229
xbuf_reset(&last_ansi);
1230
ansi_done(line_ansi);
1231
line_ansi = NULL;
1232
break;
1233
default:
1234
break;
1235
}
1236
return (0);
1237
}
1238
1239
static int store_bs(LWCHAR ch, constant char *rep, POSITION pos)
1240
{
1241
if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF))
1242
return store_control_char(ch, rep, pos);
1243
if (linebuf.end > 0 &&
1244
((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') ||
1245
(linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY))))
1246
STORE_PRCHAR('\b', pos);
1247
else if (proc_backspace == OPT_OFF && bs_mode == BS_NORMAL)
1248
STORE_CHAR(ch, AT_NORMAL, NULL, pos);
1249
else if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF))
1250
overstrike = backc();
1251
return 0;
1252
}
1253
1254
static int do_append(LWCHAR ch, constant char *rep, POSITION pos)
1255
{
1256
int a = AT_NORMAL;
1257
int in_overstrike = overstrike;
1258
1259
if ((ctldisp == OPT_ONPLUS || pos == NULL_POSITION) && line_ansi == NULL)
1260
{
1261
line_ansi = ansi_start(ch);
1262
if (line_ansi != NULL)
1263
ansi_in_line = TRUE;
1264
}
1265
1266
overstrike = 0;
1267
if (line_ansi != NULL)
1268
return store_ansi(ch, rep, pos);
1269
1270
if (ch == '\b')
1271
return store_bs(ch, rep, pos);
1272
1273
if (in_overstrike > 0)
1274
{
1275
/*
1276
* Overstrike the character at the current position
1277
* in the line buffer. This will cause either
1278
* underline (if a "_" is overstruck),
1279
* bold (if an identical character is overstruck),
1280
* or just replacing the character in the buffer.
1281
*/
1282
LWCHAR prev_ch;
1283
overstrike = utf_mode ? -1 : 0;
1284
if (utf_mode)
1285
{
1286
/* To be correct, this must be a base character. */
1287
prev_ch = get_wchar(&linebuf.buf[linebuf.end]);
1288
} else
1289
{
1290
prev_ch = (unsigned char) linebuf.buf[linebuf.end];
1291
}
1292
a = linebuf.attr[linebuf.end];
1293
if (ch == prev_ch)
1294
{
1295
/*
1296
* Overstriking a char with itself means make it bold.
1297
* But overstriking an underscore with itself is
1298
* ambiguous. It could mean make it bold, or
1299
* it could mean make it underlined.
1300
* Use the previous overstrike to resolve it.
1301
*/
1302
if (ch == '_')
1303
{
1304
if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
1305
a |= (AT_BOLD|AT_UNDERLINE);
1306
else if (last_overstrike != AT_NORMAL)
1307
a |= last_overstrike;
1308
else
1309
a |= AT_BOLD;
1310
} else
1311
a |= AT_BOLD;
1312
} else if (ch == '_')
1313
{
1314
a |= AT_UNDERLINE;
1315
ch = prev_ch;
1316
rep = &linebuf.buf[linebuf.end];
1317
} else if (prev_ch == '_')
1318
{
1319
a |= AT_UNDERLINE;
1320
}
1321
/* Else we replace prev_ch, but we keep its attributes. */
1322
} else if (in_overstrike < 0)
1323
{
1324
if ( is_composing_char(ch)
1325
|| is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch))
1326
/* Continuation of the same overstrike. */
1327
a = last_overstrike;
1328
else
1329
overstrike = 0;
1330
}
1331
1332
if (is_omit_char(ch))
1333
{
1334
if (bs_mode == BS_CONTROL)
1335
{
1336
if (utf_mode)
1337
STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1338
else
1339
STORE_PRCHAR(ch, pos);
1340
}
1341
return (0); /* omit the character. */
1342
}
1343
if (ch == '\t')
1344
{
1345
/*
1346
* Expand a tab into spaces.
1347
*/
1348
if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF))
1349
return store_control_char(ch, rep, pos);
1350
STORE_TAB(a, pos);
1351
return (0);
1352
}
1353
if ((!utf_mode || is_ascii_char(ch)) && control_char(ch))
1354
{
1355
return store_control_char(ch, rep, pos);
1356
} else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
1357
{
1358
STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1359
} else
1360
{
1361
STORE_CHAR(ch, a, rep, pos);
1362
}
1363
return (0);
1364
}
1365
1366
/*
1367
*
1368
*/
1369
public int pflushmbc(void)
1370
{
1371
int r = 0;
1372
1373
if (mbc_buf_len > 0)
1374
{
1375
/* Flush incomplete (truncated) sequence. */
1376
r = flush_mbc_buf(mbc_pos);
1377
mbc_buf_len = 0;
1378
}
1379
return r;
1380
}
1381
1382
/*
1383
* Switch to normal attribute at end of line.
1384
*/
1385
static void add_attr_normal(void)
1386
{
1387
if (line_ansi != NULL)
1388
{
1389
switch (line_ansi->ostate)
1390
{
1391
case OSC_TYPENUM:
1392
case OSC8_PARAMS:
1393
case OSC8_URI:
1394
case OSC_STRING:
1395
addstr_linebuf("\033\\", AT_ANSI, 0);
1396
break;
1397
default:
1398
break;
1399
}
1400
ansi_done(line_ansi);
1401
line_ansi = NULL;
1402
}
1403
if (ctldisp != OPT_ONPLUS || !is_ansi_end('m'))
1404
return;
1405
addstr_linebuf("\033[m", AT_ANSI, 0);
1406
if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */
1407
addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0);
1408
}
1409
1410
/*
1411
* Terminate the line in the line buffer.
1412
*/
1413
public void pdone(lbool endline, lbool chopped, lbool forw)
1414
{
1415
(void) pflushmbc();
1416
linebuf.prev_end = (!endline && !chopped) ? linebuf.end : 0;
1417
1418
if (pendc && (pendc != '\r' || !endline))
1419
/*
1420
* If we had a pending character, put it in the buffer.
1421
* But discard a pending CR if we are at end of line
1422
* (that is, discard the CR in a CR/LF sequence).
1423
*/
1424
(void) do_append(pendc, NULL, pendpos);
1425
1426
if (chopped && rscroll_char)
1427
{
1428
char rscroll_utf8[MAX_UTF_CHAR_LEN+1];
1429
char *up = rscroll_utf8;
1430
1431
/*
1432
* Display the right scrolling char.
1433
* If we've already filled the rightmost screen char
1434
* (in the buffer), overwrite it.
1435
*/
1436
if (end_column >= sc_width + cshift)
1437
{
1438
/* We've already written in the rightmost char. */
1439
end_column = right_column;
1440
linebuf.end = (size_t) right_curr;
1441
}
1442
add_attr_normal();
1443
while (end_column < sc_width-1 + cshift)
1444
{
1445
/*
1446
* Space to last (rightmost) char on screen.
1447
* This may be necessary if the char we overwrote
1448
* was double-width.
1449
*/
1450
add_linebuf(' ', 0, 1);
1451
}
1452
/* Print rscroll char. */
1453
put_wchar(&up, rscroll_char);
1454
*up = '\0';
1455
addstr_linebuf(rscroll_utf8, rscroll_attr, 0);
1456
inc_end_column(1); /* assume rscroll_char is single-width */
1457
} else
1458
{
1459
add_attr_normal();
1460
}
1461
1462
/*
1463
* If we're coloring a status line, fill out the line with spaces.
1464
*/
1465
if (status_line && line_mark_attr != 0) {
1466
while (end_column +1 < sc_width + cshift)
1467
add_linebuf(' ', line_mark_attr, 1);
1468
}
1469
1470
/*
1471
* Add a newline if necessary,
1472
* and append a '\0' to the end of the line.
1473
* We output a newline if we're not at the right edge of the screen,
1474
* or if the terminal doesn't auto wrap,
1475
* or if this is really the end of the line AND the terminal ignores
1476
* a newline at the right edge.
1477
* (In the last case we don't want to output a newline if the terminal
1478
* doesn't ignore it since that would produce an extra blank line.
1479
* But we do want to output a newline if the terminal ignores it in case
1480
* the next line is blank. In that case the single newline output for
1481
* that blank line would be ignored!)
1482
*/
1483
if (end_column < sc_width + cshift || !auto_wrap || (endline && defer_wrap) || ctldisp == OPT_ON)
1484
{
1485
add_linebuf('\n', AT_NORMAL, 0);
1486
}
1487
else if (defer_wrap && end_column >= sc_width + cshift && forw)
1488
{
1489
/*
1490
* Terminals with "defer_wrap" don't wrap until they *really* need
1491
* to, i.e. when the character *after* the last one to fit on a
1492
* line is output. But they are too hard to deal with when they
1493
* get in the state where a full screen width of characters
1494
* have been output but the cursor is sitting on the right edge
1495
* instead of at the start of the next line.
1496
* So we nudge them into wrapping by outputting a space
1497
* character plus a backspace. But do this only if moving
1498
* forward; if we're moving backward and drawing this line at
1499
* the top of the screen, the space would overwrite the first
1500
* char on the next line. We don't need to do this "nudge"
1501
* at the top of the screen anyway.
1502
*/
1503
add_linebuf(' ', AT_NORMAL, 1);
1504
add_linebuf('\b', AT_NORMAL, -1);
1505
}
1506
/*
1507
* If a terminal moves the cursor to the next line immediately after
1508
* writing into the last char of a line, the following line may get
1509
* colored with the last char's background color before the color
1510
* reset sequence is sent. Clear the line to reset the background color.
1511
*/
1512
if (auto_wrap && !defer_wrap && end_column >= sc_width + cshift)
1513
clear_after_line = TRUE;
1514
set_linebuf(linebuf.end, '\0', AT_NORMAL);
1515
}
1516
1517
/*
1518
* Return the column number (screen position) of a given file position in its line.
1519
* linepos = position of first char in line
1520
* spos = position of char being queried
1521
* saved_pos = position of a known column, or NULL_POSITION if no known column
1522
* saved_col = column number of a known column, or -1 if no known column
1523
*
1524
* This attempts to mimic the logic in pappend() and the store_*() functions.
1525
* Duplicating this complicated logic is not a good design.
1526
*/
1527
1528
struct col_pos { int col; POSITION pos; };
1529
1530
static void col_vs_pos(POSITION linepos, mutable struct col_pos *cp, POSITION saved_pos, int saved_col)
1531
{
1532
int col = (saved_col < 0) ? 0 : saved_col;
1533
LWCHAR prev_ch = 0;
1534
struct ansi_state *pansi = NULL;
1535
char utf8_buf[MAX_UTF_CHAR_LEN];
1536
int utf8_len = 0;
1537
POSITION chpos;
1538
1539
if (ch_seek(saved_pos != NULL_POSITION ? saved_pos : linepos))
1540
return;
1541
for (;;)
1542
{
1543
int ich;
1544
char ch;
1545
int cw = 0;
1546
1547
chpos = ch_tell();
1548
ich = ch_forw_get();
1549
ch = (char) ich;
1550
if (ich == EOI || ch == '\n')
1551
break;
1552
if (pansi != NULL)
1553
{
1554
if (ansi_step(pansi, ch) != ANSI_MID)
1555
{
1556
ansi_done(pansi);
1557
pansi = NULL;
1558
}
1559
} else if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(ch)) != NULL)
1560
{
1561
/* start of ansi sequence */
1562
(void) ansi_step(pansi, ch);
1563
} else if (ch == '\b')
1564
{
1565
if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF))
1566
cw = (int) strlen(prchar(ch));
1567
else
1568
cw = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
1569
} else if (ch == '\t')
1570
{
1571
if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF))
1572
cw = (int) strlen(prchar(ch));
1573
else
1574
cw = tab_spaces(col);
1575
} else if ((!utf_mode || is_ascii_char(ch)) && control_char(ch))
1576
{
1577
cw = (int) strlen(prchar(ch));
1578
} else if (utf8_len < MAX_UTF_CHAR_LEN)
1579
{
1580
utf8_buf[utf8_len++] = ch;
1581
if (is_utf8_well_formed(utf8_buf, utf8_len))
1582
{
1583
LWCHAR wch = get_wchar(utf8_buf);
1584
int attr = 0; /* {{ ignoring attribute is not correct for magic cookie terminals }} */
1585
utf8_len = 0;
1586
if (is_omit_char(wch))
1587
{
1588
if (bs_mode == BS_CONTROL)
1589
cw = strlen(utf_mode ? prutfchar(wch) : prchar(wch));
1590
} else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(wch))
1591
cw = (int) strlen(prutfchar(wch));
1592
else
1593
cw = pwidth(wch, attr, prev_ch, attr);
1594
prev_ch = wch;
1595
}
1596
} else
1597
{
1598
utf8_len = 0; /* flush invalid UTF-8 */
1599
}
1600
1601
if (cp->pos != NULL_POSITION && chpos == cp->pos) /* found the position we want */
1602
break;
1603
if (cp->col >= 0 && col >= cp->col && cw > 0) /* found the column we want */
1604
break;
1605
col += cw;
1606
prev_ch = ch;
1607
}
1608
cp->col = col;
1609
cp->pos = chpos;
1610
}
1611
1612
public int col_from_pos(POSITION linepos, POSITION spos, POSITION saved_pos, int saved_col)
1613
{
1614
struct col_pos cp;
1615
cp.pos = spos;
1616
cp.col = -1;
1617
col_vs_pos(linepos, &cp, saved_pos, saved_col);
1618
return cp.col;
1619
}
1620
1621
public POSITION pos_from_col(POSITION linepos, int col, POSITION saved_pos, int saved_col)
1622
{
1623
struct col_pos cp;
1624
cp.col = col + hshift - line_pfx_width();
1625
cp.pos = NULL_POSITION;
1626
col_vs_pos(linepos, &cp, saved_pos, saved_col);
1627
return cp.pos;
1628
}
1629
1630
/*
1631
* Set an attribute on each char of the line in the line buffer.
1632
*/
1633
public void set_attr_line(int a)
1634
{
1635
size_t i;
1636
1637
for (i = linebuf.print; i < linebuf.end; i++)
1638
if ((linebuf.attr[i] & AT_COLOR) == 0 || (a & AT_COLOR) == 0)
1639
linebuf.attr[i] |= a;
1640
}
1641
1642
/*
1643
* Set the char to be displayed in the status column.
1644
*/
1645
public void set_status_col(char c, int attr)
1646
{
1647
set_pfx(0, c, attr);
1648
}
1649
1650
/*
1651
* Get a character from the current line.
1652
* Return the character as the function return value,
1653
* and the character attribute in *ap.
1654
*/
1655
public int gline(size_t i, int *ap)
1656
{
1657
if (is_null_line)
1658
{
1659
/*
1660
* If there is no current line, we pretend the line is
1661
* either "~" or "", depending on the "twiddle" flag.
1662
*/
1663
if (twiddle)
1664
{
1665
if (i == 0)
1666
{
1667
*ap = AT_BOLD;
1668
return '~';
1669
}
1670
--i;
1671
}
1672
/* Make sure we're back to AT_NORMAL before the '\n'. */
1673
*ap = AT_NORMAL;
1674
return i ? '\0' : '\n';
1675
}
1676
1677
if (i < linebuf.pfx_end)
1678
{
1679
*ap = linebuf.pfx_attr[i];
1680
return linebuf.pfx[i];
1681
}
1682
i += linebuf.print - linebuf.pfx_end;
1683
*ap = linebuf.attr[i];
1684
return (linebuf.buf[i] & 0xFF);
1685
}
1686
1687
/*
1688
* Should we clear to end of line after printing this line?
1689
*/
1690
public lbool should_clear_after_line(void)
1691
{
1692
return clear_after_line;
1693
}
1694
1695
/*
1696
* Indicate that there is no current line.
1697
*/
1698
public void null_line(void)
1699
{
1700
is_null_line = TRUE;
1701
cshift = 0;
1702
}
1703
1704
/*
1705
* Analogous to forw_line(), but deals with "raw lines":
1706
* lines which are not split for screen width.
1707
* {{ This is supposed to be more efficient than forw_line(). }}
1708
*/
1709
public POSITION forw_raw_line_len(POSITION curr_pos, size_t read_len, constant char **linep, size_t *line_lenp)
1710
{
1711
size_t n;
1712
int c;
1713
POSITION new_pos;
1714
1715
if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
1716
(c = ch_forw_get()) == EOI)
1717
return (NULL_POSITION);
1718
1719
set_line_contig_pos(NULL_POSITION);
1720
n = 0;
1721
for (;;)
1722
{
1723
if (c == '\n' || c == EOI || ABORT_SIGS())
1724
{
1725
new_pos = ch_tell();
1726
break;
1727
}
1728
if (n >= size_linebuf-1)
1729
{
1730
if (expand_linebuf())
1731
{
1732
/*
1733
* Overflowed the input buffer.
1734
* Pretend the line ended here.
1735
*/
1736
new_pos = ch_tell() - 1;
1737
break;
1738
}
1739
}
1740
linebuf.buf[n++] = (char) c;
1741
if (read_len != size_t_null && read_len > 0 && n >= read_len)
1742
{
1743
new_pos = ch_tell();
1744
break;
1745
}
1746
c = ch_forw_get();
1747
}
1748
linebuf.buf[n] = '\0';
1749
if (linep != NULL)
1750
*linep = linebuf.buf;
1751
if (line_lenp != NULL)
1752
*line_lenp = n;
1753
return (new_pos);
1754
}
1755
1756
public POSITION forw_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp)
1757
{
1758
return forw_raw_line_len(curr_pos, size_t_null, linep, line_lenp);
1759
}
1760
1761
/*
1762
* Analogous to back_line(), but deals with "raw lines".
1763
* {{ This is supposed to be more efficient than back_line(). }}
1764
*/
1765
public POSITION back_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp)
1766
{
1767
size_t n;
1768
int c;
1769
POSITION new_pos;
1770
1771
if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
1772
ch_seek(curr_pos-1))
1773
return (NULL_POSITION);
1774
1775
set_line_contig_pos(NULL_POSITION);
1776
n = size_linebuf;
1777
linebuf.buf[--n] = '\0';
1778
for (;;)
1779
{
1780
c = ch_back_get();
1781
if (c == '\n' || ABORT_SIGS())
1782
{
1783
/*
1784
* This is the newline ending the previous line.
1785
* We have hit the beginning of the line.
1786
*/
1787
new_pos = ch_tell() + 1;
1788
break;
1789
}
1790
if (c == EOI)
1791
{
1792
/*
1793
* We have hit the beginning of the file.
1794
* This must be the first line in the file.
1795
* This must, of course, be the beginning of the line.
1796
*/
1797
new_pos = ch_zero();
1798
break;
1799
}
1800
if (n <= 0)
1801
{
1802
size_t old_size_linebuf = size_linebuf;
1803
char *fm;
1804
char *to;
1805
if (expand_linebuf())
1806
{
1807
/*
1808
* Overflowed the input buffer.
1809
* Pretend the line ended here.
1810
*/
1811
new_pos = ch_tell() + 1;
1812
break;
1813
}
1814
/*
1815
* Shift the data to the end of the new linebuf.
1816
*/
1817
for (fm = linebuf.buf + old_size_linebuf - 1,
1818
to = linebuf.buf + size_linebuf - 1;
1819
fm >= linebuf.buf; fm--, to--)
1820
*to = *fm;
1821
n = size_linebuf - old_size_linebuf;
1822
}
1823
linebuf.buf[--n] = (char) c;
1824
}
1825
if (linep != NULL)
1826
*linep = &linebuf.buf[n];
1827
if (line_lenp != NULL)
1828
*line_lenp = size_linebuf - 1 - n;
1829
return (new_pos);
1830
}
1831
1832
/*
1833
* Skip cols printable columns at the start of line.
1834
* Return number of bytes skipped.
1835
*/
1836
public int skip_columns(int cols, constant char **linep, size_t *line_lenp)
1837
{
1838
constant char *line = *linep;
1839
constant char *eline = line + *line_lenp;
1840
LWCHAR pch = 0;
1841
size_t bytes;
1842
1843
while (cols > 0 && line < eline)
1844
{
1845
LWCHAR ch = step_charc(&line, +1, eline);
1846
struct ansi_state *pansi = ansi_start(ch);
1847
if (pansi != NULL)
1848
{
1849
skip_ansi(pansi, ch, &line, eline);
1850
ansi_done(pansi);
1851
pch = 0;
1852
} else
1853
{
1854
int w = pwidth(ch, 0, pch, 0);
1855
cols -= w;
1856
pch = ch;
1857
}
1858
}
1859
bytes = ptr_diff(line, *linep);
1860
*linep = line;
1861
*line_lenp -= bytes;
1862
return (int) bytes; /*{{type-issue}}*/
1863
}
1864
1865
/*
1866
* Append a string to the line buffer.
1867
*/
1868
static int pappstr(constant char *str)
1869
{
1870
while (*str != '\0')
1871
{
1872
if (pappend(*str++, NULL_POSITION))
1873
/* Doesn't fit on screen. */
1874
return 1;
1875
}
1876
return 0;
1877
}
1878
1879
/*
1880
* Load a string into the line buffer.
1881
* If the string is too long to fit on the screen,
1882
* truncate the beginning of the string to fit.
1883
*/
1884
public void load_line(constant char *str)
1885
{
1886
int save_hshift = hshift;
1887
hshift = 0;
1888
1889
/* We're overwriting the line buffer, so what's in it will no longer be contiguous. */
1890
set_line_contig_pos(NULL_POSITION);
1891
1892
for (;;)
1893
{
1894
prewind(FALSE);
1895
if (pappstr(str) == 0)
1896
break;
1897
/*
1898
* Didn't fit on screen; increase left shift by one.
1899
* {{ This gets very inefficient if the string
1900
* is much longer than the screen width. }}
1901
*/
1902
hshift += 1;
1903
}
1904
set_linebuf(linebuf.end, '\0', AT_NORMAL);
1905
linebuf.prev_end = 0;
1906
1907
/* Color the prompt unless it has ansi sequences in it. */
1908
if (!ansi_in_line)
1909
{
1910
size_t i;
1911
for (i = linebuf.print; i < linebuf.end; i++)
1912
set_linebuf(i, linebuf.buf[i], AT_STANDOUT|AT_COLOR_PROMPT);
1913
}
1914
hshift = save_hshift;
1915
}
1916
1917
/*
1918
* Find the length of the longest displayed line on the screen.
1919
*/
1920
public int longest_line_width(void)
1921
{
1922
POSITION pos;
1923
int save_width;
1924
int sindex;
1925
int longest = 0;
1926
1927
save_width = sc_width;
1928
sc_width = INT_MAX; /* so forw_line() won't chop */
1929
for (sindex = TOP; sindex < sc_height-1; sindex++)
1930
if ((pos = position(sindex)) != NULL_POSITION)
1931
break;
1932
for (; sindex < sc_height-1 && pos != NULL_POSITION; sindex++)
1933
{
1934
pos = forw_line(pos, NULL, NULL);
1935
if (end_column > longest)
1936
longest = end_column;
1937
}
1938
sc_width = save_width;
1939
return longest;
1940
}
1941
1942
/*
1943
* Find the shift necessary to show the end of the longest displayed line.
1944
*/
1945
public int rrshift(void)
1946
{
1947
int longest = longest_line_width();
1948
if (longest < sc_width)
1949
return 0;
1950
return longest - sc_width;
1951
}
1952
1953
/*
1954
* Get the color_map index associated with a given attribute.
1955
*/
1956
static int lookup_color_index(int attr)
1957
{
1958
int cx;
1959
for (cx = 0; cx < countof(color_map); cx++)
1960
if (color_map[cx].attr == attr)
1961
return cx;
1962
return -1;
1963
}
1964
1965
static int color_index(int attr)
1966
{
1967
if (use_color && (attr & AT_COLOR))
1968
return lookup_color_index(attr & AT_COLOR);
1969
if (attr & AT_UNDERLINE)
1970
return lookup_color_index(AT_UNDERLINE);
1971
if (attr & AT_BOLD)
1972
return lookup_color_index(AT_BOLD);
1973
if (attr & AT_BLINK)
1974
return lookup_color_index(AT_BLINK);
1975
if (attr & AT_STANDOUT)
1976
return lookup_color_index(AT_STANDOUT);
1977
return -1;
1978
}
1979
1980
/*
1981
* Set the color string to use for a given attribute.
1982
*/
1983
public int set_color_map(int attr, constant char *colorstr)
1984
{
1985
int cx = color_index(attr);
1986
if (cx < 0)
1987
return -1;
1988
if (strlen(colorstr)+1 > sizeof(color_map[cx].color))
1989
return -1;
1990
if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL, NULL) == CT_NULL)
1991
return -1;
1992
strcpy(color_map[cx].color, colorstr);
1993
return 0;
1994
}
1995
1996
/*
1997
* Get the color string to use for a given attribute.
1998
*/
1999
public constant char * get_color_map(int attr)
2000
{
2001
int cx = color_index(attr);
2002
if (cx < 0)
2003
return NULL;
2004
return color_map[cx].color;
2005
}
2006
2007