1 /* Plain text document renderer */
12 #include "bookmarks/bookmarks.h"
13 #include "cache/cache.h"
14 #include "config/options.h"
15 #include "document/docdata.h"
16 #include "document/document.h"
17 #include "document/options.h"
18 #include "document/plain/renderer.h"
19 #include "document/renderer.h"
20 #include "globhist/globhist.h"
21 #include "intl/charsets.h"
22 #include "protocol/protocol.h"
23 #include "protocol/uri.h"
24 #include "terminal/color.h"
25 #include "terminal/draw.h"
26 #include "util/color.h"
27 #include "util/error.h"
28 #include "util/memory.h"
29 #include "util/string.h"
32 struct plain_renderer
{
33 /* The document being renderered */
34 struct document
*document
;
36 /* The data and data length of the defragmented cache entry */
37 unsigned char *source
;
40 /* The convert table that should be used for converting line strings to
41 * the rendered strings. */
42 struct conv_table
*convert_table
;
44 /* The default template char data for text */
45 struct screen_char
template;
47 /* The maximum width any line can have (used for wrapping text) */
50 /* The current line number */
53 /* Are we doing line compression */
54 unsigned int compress
:1;
57 #define realloc_document_links(doc, size) \
58 ALIGN_LINK(&(doc)->links, (doc)->nlinks, size)
60 static struct screen_char
*
61 realloc_line(struct document
*document
, int x
, int y
)
63 struct line
*line
= realloc_lines(document
, y
);
65 if (!line
) return NULL
;
67 if (x
!= line
->length
) {
68 if (!ALIGN_LINE(&line
->chars
, line
->length
, x
))
77 static inline struct link
*
78 add_document_link(struct document
*document
, unsigned char *uri
, int length
,
84 if (!realloc_document_links(document
, document
->nlinks
+ 1))
87 link
= &document
->links
[document
->nlinks
];
89 if (!realloc_points(link
, length
))
92 link
->npoints
= length
;
93 link
->type
= LINK_HYPERTEXT
;
95 link
->color
.background
= document
->options
.default_bg
;
96 link
->color
.foreground
= document
->options
.default_link
;
97 link
->number
= document
->nlinks
;
99 for (point
= link
->points
; length
> 0; length
--, point
++, x
++) {
109 /* Searches a word to find an email adress or an URI to add as a link. */
110 static inline struct link
*
111 check_link_word(struct document
*document
, unsigned char *uri
, int length
,
115 unsigned char *where
= NULL
;
116 unsigned char *mailto
= memchr(uri
, '@', length
);
117 int keep
= uri
[length
];
118 struct link
*new_link
;
121 if_assert_failed
return NULL
;
125 if (mailto
&& mailto
> uri
&& mailto
- uri
< length
- 1) {
126 where
= straconcat("mailto:", uri
, NULL
);
128 } else if (parse_uri(&test
, uri
) == URI_ERRNO_OK
129 && test
.protocol
!= PROTOCOL_UNKNOWN
130 && (test
.datalen
|| test
.hostlen
)) {
131 where
= memacpy(uri
, length
);
136 if (!where
) return NULL
;
138 new_link
= add_document_link(document
, where
, length
, x
, y
);
140 if (!new_link
) mem_free(where
);
145 #define url_char(c) ( \
154 get_uri_length(unsigned char *line
, int length
)
158 while (uri_end
< length
159 && url_char(line
[uri_end
]))
162 for (; uri_end
> 0; uri_end
--) {
163 if (line
[uri_end
- 1] != '.'
164 && line
[uri_end
- 1] != ',')
172 print_document_link(struct plain_renderer
*renderer
, int lineno
,
173 unsigned char *line
, int line_pos
, int width
,
174 int expanded
, struct screen_char
*pos
)
176 struct document
*document
= renderer
->document
;
177 unsigned char *start
= &line
[line_pos
];
178 int len
= get_uri_length(start
, width
- line_pos
);
179 int screen_column
= line_pos
+ expanded
;
180 struct link
*new_link
;
181 int link_end
= line_pos
+ len
;
182 unsigned char saved_char
;
183 struct document_options
*doc_opts
= &document
->options
;
184 struct screen_char
template = renderer
->template;
189 new_link
= check_link_word(document
, start
, len
, screen_column
,
192 if (!new_link
) return 0;
194 saved_char
= line
[link_end
];
195 line
[link_end
] = '\0';
198 ; /* Shut up compiler */
199 #ifdef CONFIG_GLOBHIST
200 else if (get_global_history_item(start
))
201 new_link
->color
.foreground
= doc_opts
->default_vlink
;
203 #ifdef CONFIG_BOOKMARKS
204 else if (get_bookmark(start
))
205 new_link
->color
.foreground
= doc_opts
->default_bookmark_link
;
208 new_link
->color
.foreground
= doc_opts
->default_link
;
210 line
[link_end
] = saved_char
;
212 new_link
->color
.background
= doc_opts
->default_bg
;
214 set_term_color(&template, &new_link
->color
,
215 doc_opts
->color_flags
, doc_opts
->color_mode
);
217 for (i
= len
; i
; i
--) {
218 template.data
= line
[line_pos
++];
219 copy_screen_chars(pos
++, &template, 1);
226 add_document_line(struct plain_renderer
*renderer
,
227 unsigned char *line
, int line_width
)
229 struct document
*document
= renderer
->document
;
230 struct screen_char
*template = &renderer
->template;
231 struct screen_char saved_renderer_template
= *template;
232 struct screen_char
*pos
, *startpos
;
233 int lineno
= renderer
->lineno
;
235 int width
= line_width
;
238 line
= convert_string(renderer
->convert_table
, line
, width
,
239 document
->options
.cp
, CSM_NONE
, &width
,
243 /* Now expand tabs */
244 for (line_pos
= 0; line_pos
< width
; line_pos
++) {
245 unsigned char line_char
= line
[line_pos
];
247 if (line_char
== ASCII_TAB
248 && (line_pos
+ 1 == width
249 || line
[line_pos
+ 1] != ASCII_BS
)) {
250 int tab_width
= 7 - ((line_pos
+ expanded
) & 7);
252 expanded
+= tab_width
;
253 } else if (line_char
== ASCII_BS
) {
255 This does
not work
: Suppose we have seventeen spaces
256 followed by a back
-space
; that will call
for sixteen
257 bytes of memory
, but we will print seventeen spaces
258 before we hit the back
-space
-- overflow
!
260 /* Don't count the character
261 * that the back-space character will delete */
262 if (expanded
+ line_pos
)
266 /* Don't count the back-space character */
273 assert(expanded
>= 0);
275 startpos
= pos
= realloc_line(document
, width
+ expanded
, lineno
);
282 for (line_pos
= 0; line_pos
< width
; line_pos
++) {
283 unsigned char line_char
= line
[line_pos
];
284 unsigned char next_char
, prev_char
;
286 prev_char
= line_pos
> 0 ? line
[line_pos
- 1] : '\0';
287 next_char
= (line_pos
+ 1 < width
) ? line
[line_pos
+ 1]
290 /* Do not expand tabs that precede back-spaces; this saves the
291 * back-space code some trouble. */
292 if (line_char
== ASCII_TAB
&& next_char
!= ASCII_BS
) {
293 int tab_width
= 7 - ((line_pos
+ expanded
) & 7);
295 expanded
+= tab_width
;
297 template->data
= ' ';
299 copy_screen_chars(pos
++, template, 1);
302 *template = saved_renderer_template
;
304 } else if (line_char
== ASCII_BS
) {
305 if (!(expanded
+ line_pos
)) {
306 /* We've backspaced to the start of the line */
311 pos
--; /* Backspace */
313 /* Handle x^H_ as _^Hx, but prevent an infinite loop
314 * swapping two underscores. */
315 if (next_char
== '_' && prev_char
!= '_') {
316 /* x^H_ becomes _^Hx */
317 if (line_pos
- 1 >= 0)
318 line
[line_pos
- 1] = next_char
;
319 if (line_pos
+ 1 < width
)
320 line
[line_pos
+ 1] = prev_char
;
322 /* Go back and reparse the swapped characters */
323 if (line_pos
- 2 >= 0)
328 if ((expanded
+ line_pos
) - 2 >= 0) {
329 /* Don't count the backspace character or the
330 * deleted character when returning the line's
331 * width or when expanding tabs. */
335 if (pos
->data
== '_' && next_char
== '_') {
336 /* Is _^H_ an underlined underscore
337 * or an emboldened underscore? */
339 if (expanded
+ line_pos
>= 0
340 && pos
- 1 >= startpos
341 && (pos
- 1)->attr
) {
342 /* There is some preceding text,
343 * and it has an attribute; copy it */
344 template->attr
|= (pos
- 1)->attr
;
346 /* Default to bold; seems more useful
347 * than underlining the underscore */
348 template->attr
|= SCREEN_ATTR_BOLD
;
351 } else if (pos
->data
== '_') {
354 template->attr
|= SCREEN_ATTR_UNDERLINE
;
356 } else if (pos
->data
== next_char
) {
359 template->attr
|= SCREEN_ATTR_BOLD
;
362 /* Handle _^Hx^Hx as both bold and underlined */
364 template->attr
|= pos
->attr
;
368 if (document
->options
.plain_display_links
369 && isalpha(line_char
) && isalpha(next_char
)) {
370 /* We only want to check for a URI if there are
371 * at least two consecutive alphabetic
372 * characters, or if we are at the very start of
373 * the line. It improves performance a bit.
375 added_chars
= print_document_link(renderer
,
384 line_pos
+= added_chars
- 1;
387 if (!isscreensafe(line_char
))
389 template->data
= line_char
;
390 copy_screen_chars(pos
++, template, 1);
392 /* Detect copy of nul chars to screen, this
393 * should not occur. --Zas */
397 *template = saved_renderer_template
;
403 realloc_line(document
, pos
- startpos
, lineno
);
405 return width
+ expanded
;
409 init_template(struct screen_char
*template, struct document_options
*options
)
411 color_T background
= options
->default_bg
;
412 color_T foreground
= options
->default_fg
;
413 struct color_pair colors
= INIT_COLOR_PAIR(background
, foreground
);
416 template->data
= ' ';
417 set_term_color(template, &colors
,
418 options
->color_flags
, options
->color_mode
);
422 add_node(struct plain_renderer
*renderer
, int x
, int width
, int height
)
424 struct node
*node
= mem_alloc(sizeof(*node
));
427 struct document
*document
= renderer
->document
;
429 set_box(&node
->box
, x
, renderer
->lineno
, width
, height
);
431 int_lower_bound(&document
->width
, width
);
432 int_lower_bound(&document
->height
, height
);
434 add_to_list(document
->nodes
, node
);
441 add_document_lines(struct plain_renderer
*renderer
)
443 unsigned char *source
= renderer
->source
;
444 int length
= renderer
->length
;
445 int was_empty_line
= 0;
447 for (; length
> 0; renderer
->lineno
++) {
448 unsigned char *xsource
;
449 int width
, added
, only_spaces
= 1, spaces
= 0, was_spaces
= 0;
452 int doc_width
= int_min(renderer
->max_width
, length
);
454 /* End of line detection: We handle \r, \r\n and \n types. */
455 for (width
= 0; width
< doc_width
; width
++) {
456 if (source
[width
] == ASCII_CR
)
458 if (source
[width
+ step
] == ASCII_LF
)
462 if (isspace(source
[width
])) {
474 if (only_spaces
&& step
) {
475 if (renderer
->compress
&& was_empty_line
) {
476 /* Successive empty lines will appear as one. */
477 length
-= step
+ spaces
;
478 source
+= step
+ spaces
;
480 assert(renderer
->lineno
>= 0);
485 /* No need to keep whitespaces on an empty line. */
493 if (was_spaces
&& step
) {
494 /* Drop trailing whitespaces. */
498 if (!step
&& (width
< length
) && last_space
) {
506 /* We will touch the supplied source, so better replicate it. */
507 xsource
= memacpy(source
, width
);
508 if (!xsource
) continue;
510 added
= add_document_line(renderer
, xsource
, width
);
514 /* Add (search) nodes on a line by line basis */
515 add_node(renderer
, 0, added
, 1);
518 /* Skip end of line chars too. */
528 render_plain_document(struct cache_entry
*cached
, struct document
*document
,
529 struct string
*buffer
)
531 struct conv_table
*convert_table
;
532 unsigned char *head
= empty_string_or_(cached
->head
);
533 struct plain_renderer renderer
;
535 convert_table
= get_convert_table(head
, document
->options
.cp
,
536 document
->options
.assume_cp
,
538 &document
->cp_status
,
539 document
->options
.hard_assume
);
541 renderer
.source
= buffer
->source
;
542 renderer
.length
= buffer
->length
;
544 renderer
.document
= document
;
546 renderer
.convert_table
= convert_table
;
547 renderer
.compress
= document
->options
.plain_compress_empty_lines
;
548 renderer
.max_width
= document
->options
.wrap
? document
->options
.box
.width
551 document
->bgcolor
= document
->options
.default_bg
;
554 /* Setup the style */
555 init_template(&renderer
.template, &document
->options
);
557 add_document_lines(&renderer
);