1 /* Plain text document renderer */
13 #include "bookmarks/bookmarks.h"
14 #include "cache/cache.h"
15 #include "config/options.h"
16 #include "document/docdata.h"
17 #include "document/document.h"
18 #include "document/format.h"
19 #include "document/options.h"
20 #include "document/plain/renderer.h"
21 #include "document/renderer.h"
22 #include "globhist/globhist.h"
23 #include "intl/charsets.h"
24 #include "protocol/protocol.h"
25 #include "protocol/uri.h"
26 #include "terminal/color.h"
27 #include "terminal/draw.h"
28 #include "util/color.h"
29 #include "util/error.h"
30 #include "util/memory.h"
31 #include "util/string.h"
34 struct plain_renderer
{
35 /* The document being renderered */
36 struct document
*document
;
38 /* The data and data length of the defragmented cache entry */
39 unsigned char *source
;
42 /* The convert table that should be used for converting line strings to
43 * the rendered strings. */
44 struct conv_table
*convert_table
;
46 /* The default template char data for text */
47 struct screen_char
template;
49 /* The maximum width any line can have (used for wrapping text) */
52 /* The current line number */
55 /* Are we doing line compression */
56 unsigned int compress
:1;
59 #define realloc_document_links(doc, size) \
60 ALIGN_LINK(&(doc)->links, (doc)->nlinks, size)
62 static struct screen_char
*
63 realloc_line(struct document
*document
, int x
, int y
)
65 struct line
*line
= realloc_lines(document
, y
);
67 if (!line
) return NULL
;
69 if (x
!= line
->length
) {
70 if (!ALIGN_LINE(&line
->chars
, line
->length
, x
))
79 static inline struct link
*
80 add_document_link(struct document
*document
, unsigned char *uri
, int length
,
86 if (!realloc_document_links(document
, document
->nlinks
+ 1))
89 link
= &document
->links
[document
->nlinks
];
91 if (!realloc_points(link
, length
))
94 link
->npoints
= length
;
95 link
->type
= LINK_HYPERTEXT
;
97 link
->color
.background
= document
->options
.default_style
.color
.background
;
98 link
->color
.foreground
= document
->options
.default_color
.link
;
99 link
->number
= document
->nlinks
;
101 for (point
= link
->points
; length
> 0; length
--, point
++, x
++) {
107 document
->links_sorted
= 0;
111 /* Searches a word to find an email adress or an URI to add as a link. */
112 static inline struct link
*
113 check_link_word(struct document
*document
, unsigned char *uri
, int length
,
117 unsigned char *where
= NULL
;
118 unsigned char *mailto
= memchr(uri
, '@', length
);
119 int keep
= uri
[length
];
120 struct link
*new_link
;
123 if_assert_failed
return NULL
;
127 if (mailto
&& mailto
> uri
&& mailto
- uri
< length
- 1) {
128 where
= straconcat("mailto:", uri
, (unsigned char *) NULL
);
130 } else if (parse_uri(&test
, uri
) == URI_ERRNO_OK
131 && test
.protocol
!= PROTOCOL_UNKNOWN
132 && (test
.datalen
|| test
.hostlen
)) {
133 where
= memacpy(uri
, length
);
138 if (!where
) return NULL
;
140 /* We need to reparse the URI and normalize it so that the protocol and
141 * host part are converted to lowercase. */
142 normalize_uri(NULL
, where
);
144 new_link
= add_document_link(document
, where
, length
, x
, y
);
146 if (!new_link
) mem_free(where
);
151 #define url_char(c) ( \
160 get_uri_length(unsigned char *line
, int length
)
164 while (uri_end
< length
165 && url_char(line
[uri_end
]))
168 for (; uri_end
> 0; uri_end
--) {
169 if (line
[uri_end
- 1] != '.'
170 && line
[uri_end
- 1] != ',')
178 print_document_link(struct plain_renderer
*renderer
, int lineno
,
179 unsigned char *line
, int line_pos
, int width
,
180 int expanded
, struct screen_char
*pos
, int cells
)
182 struct document
*document
= renderer
->document
;
183 unsigned char *start
= &line
[line_pos
];
184 int len
= get_uri_length(start
, width
- line_pos
);
185 int screen_column
= cells
+ expanded
;
186 struct link
*new_link
;
187 int link_end
= line_pos
+ len
;
188 unsigned char saved_char
;
189 struct document_options
*doc_opts
= &document
->options
;
190 struct screen_char
template = renderer
->template;
195 new_link
= check_link_word(document
, start
, len
, screen_column
,
198 if (!new_link
) return 0;
200 saved_char
= line
[link_end
];
201 line
[link_end
] = '\0';
204 ; /* Shut up compiler */
205 #ifdef CONFIG_GLOBHIST
206 else if (get_global_history_item(start
))
207 new_link
->color
.foreground
= doc_opts
->default_color
.vlink
;
209 #ifdef CONFIG_BOOKMARKS
210 else if (get_bookmark(start
))
211 new_link
->color
.foreground
= doc_opts
->default_color
.bookmark_link
;
214 new_link
->color
.foreground
= doc_opts
->default_color
.link
;
216 line
[link_end
] = saved_char
;
218 new_link
->color
.background
= doc_opts
->default_style
.color
.background
;
220 set_term_color(&template, &new_link
->color
,
221 doc_opts
->color_flags
, doc_opts
->color_mode
);
223 for (i
= len
; i
; i
--) {
224 template.data
= line
[line_pos
++];
225 copy_screen_chars(pos
++, &template, 1);
232 decode_esc_color(unsigned char *text
, int *line_pos
, int width
,
233 struct screen_char
*template, enum color_mode mode
,
236 struct screen_char ch
;
237 struct color_pair color
;
238 char *buf
, *tail
, *begin
, *end
;
239 int k
, foreground
, background
, f1
, b1
; /* , intensity; */
242 buf
= (char *)&text
[*line_pos
];
244 if (*buf
!= '[') return;
248 k
= strspn(buf
, "0123456789;");
250 if (!k
|| buf
[k
] != 'm') return;
255 get_screen_char_color(template, &color
, 0, mode
);
256 set_term_color(&ch
, &color
, 0, COLOR_MODE_16
);
257 b1
= background
= (ch
.c
.color
[0] >> 4) & 7;
258 f1
= foreground
= ch
.c
.color
[0] & 15;
261 unsigned char kod
= (unsigned char)strtol(begin
, &tail
, 10);
270 if (*was_reversed
== 0) {
277 if (*was_reversed
== 1) {
291 foreground
= kod
- 30;
301 background
= kod
- 40;
307 color
.background
= get_term_color16(background
);
308 color
.foreground
= get_term_color16(foreground
);
309 set_term_color(template, &color
, 0, mode
);
313 add_document_line(struct plain_renderer
*renderer
,
314 unsigned char *line
, int line_width
)
316 struct document
*document
= renderer
->document
;
317 struct screen_char
*template = &renderer
->template;
318 struct screen_char saved_renderer_template
= *template;
319 struct screen_char
*pos
, *startpos
;
320 struct document_options
*doc_opts
= &document
->options
;
321 int was_reversed
= 0;
324 int utf8
= doc_opts
->utf8
;
325 #endif /* CONFIG_UTF8 */
327 int lineno
= renderer
->lineno
;
329 int width
= line_width
;
332 line
= convert_string(renderer
->convert_table
, line
, width
,
333 document
->options
.cp
, CSM_NONE
, &width
,
337 /* Now expand tabs */
338 for (line_pos
= 0; line_pos
< width
;) {
339 unsigned char line_char
= line
[line_pos
];
346 unsigned char *line_char2
= &line
[line_pos
];
347 charlen
= utf8charlen(&line_char
);
348 data
= utf8_to_unicode(&line_char2
, &line
[width
]);
350 if (data
== UCS_NO_CHAR
) {
355 cell
= unicode_to_cell(data
);
357 #endif /* CONFIG_UTF8 */
359 if (line_char
== ASCII_TAB
360 && (line_pos
+ charlen
== width
361 || line
[line_pos
+ charlen
] != ASCII_BS
)) {
362 int tab_width
= 7 - ((cells
+ expanded
) & 7);
364 expanded
+= tab_width
;
365 } else if (line_char
== ASCII_BS
) {
367 This does
not work
: Suppose we have seventeen spaces
368 followed by a back
-space
; that will call
for sixteen
369 bytes of memory
, but we will print seventeen spaces
370 before we hit the back
-space
-- overflow
!
372 /* Don't count the character
373 * that the back-space character will delete */
374 if (expanded
+ line_pos
)
378 /* Don't count the back-space character */
387 assert(expanded
>= 0);
389 startpos
= pos
= realloc_line(document
, width
+ expanded
, lineno
);
397 for (line_pos
= 0; line_pos
< width
;) {
398 unsigned char line_char
= line
[line_pos
];
399 unsigned char next_char
, prev_char
;
403 unicode_val_T data
= UCS_NO_CHAR
;
406 unsigned char *line_char2
= &line
[line_pos
];
407 charlen
= utf8charlen(&line_char
);
408 data
= utf8_to_unicode(&line_char2
, &line
[width
]);
410 if (data
== UCS_NO_CHAR
) {
415 cell
= unicode_to_cell(data
);
417 #endif /* CONFIG_UTF8 */
419 prev_char
= line_pos
> 0 ? line
[line_pos
- 1] : '\0';
420 next_char
= (line_pos
+ charlen
< width
) ?
421 line
[line_pos
+ charlen
] : '\0';
423 /* Do not expand tabs that precede back-spaces; this saves the
424 * back-space code some trouble. */
425 if (line_char
== ASCII_TAB
&& next_char
!= ASCII_BS
) {
426 int tab_width
= 7 - ((cells
+ expanded
) & 7);
428 expanded
+= tab_width
;
430 template->data
= ' ';
432 copy_screen_chars(pos
++, template, 1);
435 *template = saved_renderer_template
;
437 } else if (line_char
== ASCII_BS
) {
438 if (!(expanded
+ cells
)) {
439 /* We've backspaced to the start of the line */
443 pos
--; /* Backspace */
445 /* Handle x^H_ as _^Hx, but prevent an infinite loop
446 * swapping two underscores. */
447 if (next_char
== '_' && prev_char
!= '_') {
448 /* x^H_ becomes _^Hx */
449 if (line_pos
- 1 >= 0)
450 line
[line_pos
- 1] = next_char
;
451 if (line_pos
+ charlen
< width
)
452 line
[line_pos
+ charlen
] = prev_char
;
454 /* Go back and reparse the swapped characters */
455 if (line_pos
- 2 >= 0) {
462 if ((expanded
+ line_pos
) - 2 >= 0) {
463 /* Don't count the backspace character or the
464 * deleted character when returning the line's
465 * width or when expanding tabs. */
469 if (pos
->data
== '_' && next_char
== '_') {
470 /* Is _^H_ an underlined underscore
471 * or an emboldened underscore? */
473 if (expanded
+ line_pos
>= 0
474 && pos
- 1 >= startpos
475 && (pos
- 1)->attr
) {
476 /* There is some preceding text,
477 * and it has an attribute; copy it */
478 template->attr
|= (pos
- 1)->attr
;
480 /* Default to bold; seems more useful
481 * than underlining the underscore */
482 template->attr
|= SCREEN_ATTR_BOLD
;
485 } else if (pos
->data
== '_') {
488 template->attr
|= SCREEN_ATTR_UNDERLINE
;
490 } else if (pos
->data
== next_char
) {
493 template->attr
|= SCREEN_ATTR_BOLD
;
496 /* Handle _^Hx^Hx as both bold and underlined */
498 template->attr
|= pos
->attr
;
499 } else if (line_char
== 27) {
500 decode_esc_color(line
, &line_pos
, width
,
501 &saved_renderer_template
,
502 doc_opts
->color_mode
, &was_reversed
);
503 *template = saved_renderer_template
;
507 if (document
->options
.plain_display_links
508 && isalpha(line_char
) && isalpha(next_char
)) {
509 /* We only want to check for a URI if there are
510 * at least two consecutive alphabetic
511 * characters, or if we are at the very start of
512 * the line. It improves performance a bit.
514 added_chars
= print_document_link(renderer
,
523 line_pos
+= added_chars
- 1;
524 cells
+= added_chars
- 1;
529 if (data
== UCS_NO_CHAR
) {
534 template->data
= (unicode_val_T
)data
;
535 copy_screen_chars(pos
++, template, 1);
538 template->data
= UCS_NO_CHAR
;
539 copy_screen_chars(pos
++,
543 #endif /* CONFIG_UTF8 */
545 if (!isscreensafe(line_char
))
547 template->data
= line_char
;
548 copy_screen_chars(pos
++, template, 1);
550 /* Detect copy of nul chars to screen,
551 * this should not occur. --Zas */
556 *template = saved_renderer_template
;
564 realloc_line(document
, pos
- startpos
, lineno
);
566 return width
+ expanded
;
570 init_template(struct screen_char
*template, struct document_options
*options
)
572 get_screen_char_template(template, options
, options
->default_style
);
576 add_node(struct plain_renderer
*renderer
, int x
, int width
, int height
)
578 struct node
*node
= mem_alloc(sizeof(*node
));
581 struct document
*document
= renderer
->document
;
583 set_box(&node
->box
, x
, renderer
->lineno
, width
, height
);
585 int_lower_bound(&document
->width
, width
);
586 int_lower_bound(&document
->height
, height
);
588 add_to_list(document
->nodes
, node
);
595 add_document_lines(struct plain_renderer
*renderer
)
597 unsigned char *source
= renderer
->source
;
598 int length
= renderer
->length
;
599 int was_empty_line
= 0;
602 int utf8
= is_cp_utf8(renderer
->document
->cp
);
604 for (; length
> 0; renderer
->lineno
++) {
605 unsigned char *xsource
;
606 int width
, added
, only_spaces
= 1, spaces
= 0, was_spaces
= 0;
612 /* End of line detection: We handle \r, \r\n and \n types. */
613 for (width
= 0; (width
< length
) &&
614 (cells
< renderer
->max_width
);) {
615 if (source
[width
] == ASCII_CR
)
617 if (source
[width
+ step
] == ASCII_LF
)
621 if (isspace(source
[width
])) {
627 if (source
[width
] == '\t')
628 tab_spaces
+= 7 - ((width
+ tab_spaces
) % 8);
635 unsigned char *text
= &source
[width
];
636 unicode_val_T data
= utf8_to_unicode(&text
,
639 if (data
== UCS_NO_CHAR
) return;
641 cells
+= unicode_to_cell(data
);
642 width
+= utf8charlen(&source
[width
]);
644 #endif /* CONFIG_UTF8 */
651 if (only_spaces
&& step
) {
652 if (was_wrapped
|| (renderer
->compress
&& was_empty_line
)) {
653 /* Successive empty lines will appear as one. */
654 length
-= step
+ spaces
;
655 source
+= step
+ spaces
;
657 assert(renderer
->lineno
>= 0);
662 /* No need to keep whitespaces on an empty line. */
671 if (was_spaces
&& step
) {
672 /* Drop trailing whitespaces. */
677 if (!step
&& (width
< length
) && last_space
) {
685 /* We will touch the supplied source, so better replicate it. */
686 xsource
= memacpy(source
, width
);
687 if (!xsource
) continue;
689 added
= add_document_line(renderer
, xsource
, width
);
693 /* Add (search) nodes on a line by line basis */
694 add_node(renderer
, 0, added
, 1);
697 /* Skip end of line chars too. */
707 render_plain_document(struct cache_entry
*cached
, struct document
*document
,
708 struct string
*buffer
)
710 struct conv_table
*convert_table
;
711 unsigned char *head
= empty_string_or_(cached
->head
);
712 struct plain_renderer renderer
;
714 convert_table
= get_convert_table(head
, document
->options
.cp
,
715 document
->options
.assume_cp
,
717 &document
->cp_status
,
718 document
->options
.hard_assume
);
720 renderer
.source
= buffer
->source
;
721 renderer
.length
= buffer
->length
;
723 renderer
.document
= document
;
725 renderer
.convert_table
= convert_table
;
726 renderer
.compress
= document
->options
.plain_compress_empty_lines
;
727 renderer
.max_width
= document
->options
.wrap
? document
->options
.box
.width
730 document
->color
.background
= document
->options
.default_style
.color
.background
;
733 document
->options
.utf8
= is_cp_utf8(document
->options
.cp
);
734 #endif /* CONFIG_UTF8 */
736 /* Setup the style */
737 init_template(&renderer
.template, &document
->options
);
739 add_document_lines(&renderer
);