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