2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * render-format. Provide 'render-line' functions to render
6 * a document one element per line using a format string to display
7 * attributes of that element.
9 * This is particularly used for directories and the document list.
17 #define PANE_DATA_TYPE struct rf_data
18 #define PANE_DATA_VOID_2
19 #define DOC_NEXT(p,m,r,b) format_next_prev(p, ci->focus, m, r, 1, b)
20 #define DOC_PREV(p,m,r,b) format_next_prev(p, ci->focus, m, r, 0, b)
21 #define DOC_NEXT_DECL(p,m,r,b) format_next_prev(p, struct pane *focus safe, m, r, int forward, b)
22 #define DOC_PREV_DECL(p,m,r,b) format_next_prev(p, struct pane *focus safe, m, r, int forward, b)
29 unsigned short nfields;
30 unsigned short alloc_fields;
32 /* 'field' can end at most one attribute, start at most one,
33 * can contain one text source, either var or literal.
35 const char *val safe; /* pointer into 'format' */
36 const char *attr; /* pointer into 'format', or NULL */
37 unsigned short attr_end;/* field where this attr ends */
38 unsigned short attr_start;/* starting field for attr which ends here */
39 unsigned short val_len; /* length in 'format' */
40 unsigned short attr_depth;
41 short width; /* min display width */
42 unsigned short min_attr_depth; /* attr depth of first attr - from 0 */
43 bool var; /* else constant */
44 char align; /* l,r,c */
51 #include "core-pane.h"
53 static inline short FIELD_NUM(int i) { return i >> 16; }
54 static inline short FIELD_OFFSET(int i) { return i & 0xFFFF; }
55 static inline unsigned int MAKE_INDEX(short f, short i) { return (int)f << 16 | i;}
57 static char *do_format(struct pane *focus safe,
58 struct mark *m safe, struct mark *pm,
61 char *body = pane_attr_get(focus, "line-format");
65 if (pm && !mark_same(pm, m))
74 if (len >= 0 && ret.len >= len)
78 char buf[40], *b, *val;
81 if (!attrs && *n == '<' && n[1] != '<') {
82 /* an attribute, skip it */
84 while (*n && *n != '>')
90 if (*n != '%' || n[1] == '%') {
91 buf_append_byte(&ret, *n);
98 if (len >= 0 && ret.len >= len)
104 while (*n == '-' || *n == '_' || isalnum(*n)) {
105 if (b < buf + sizeof(buf) - 2)
113 val = pane_mark_attr(focus, m, buf);
119 if (*val == '<' && attrs)
120 buf_append_byte(&ret, '<');
121 buf_append_byte(&ret, *val);
131 w = w * 10 + (*n - '0');
132 else if (w == 0 && *n == '-')
138 while (adjust && w > l) {
139 buf_append(&ret, ' ');
143 while (*val && w > 0 ) {
144 if (*val == '<' && attrs)
145 buf_append_byte(&ret, '<');
146 buf_append_byte(&ret, *val);
151 buf_append(&ret, ' ');
158 buf_append(&ret, '\n');
160 return buf_final(&ret);
163 DEF_CMD(format_content)
167 if (!ci->mark || !ci->comm2)
170 /* Cannot handle bytes */
173 m = mark_dup(ci->mark);
174 while (doc_following(ci->focus, m) != WEOF) {
178 l = do_format(ci->focus, m, NULL, -1, 0);
181 doc_next(ci->focus, m);
184 w = get_utf8(&c, NULL);
186 comm_call(ci->comm2, "consume", ci->focus, w, m) <= 0)
200 struct mark *m = ci->mark;
201 struct mark *pm = ci->mark2;
208 if (doc_following(ci->focus, ci->mark) == WEOF)
211 if (pm && !mark_same(pm, m))
217 ret = do_format(ci->focus, ci->mark, pm, len, 1);
219 doc_next(ci->focus, m);
220 rv = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, ret);
225 DEF_CMD(render_line_prev)
227 struct mark *m = ci->mark;
231 if (RPT_NUM(ci) == 0)
232 /* always at start-of-line */
234 if (doc_prev(ci->focus, m) == WEOF)
235 /* Hit start-of-file */
240 DEF_CMD_CLOSED(format_close)
242 struct rf_data *rf = ci->home->data;
244 free(rf->attr_cache); rf->attr_cache = NULL;
245 free(rf->fields); rf->fields = NULL;
246 free(rf->format); rf->format = "";
251 static struct rf_field *new_field(struct rf_data *rd safe)
255 if (rd->nfields >= rd->alloc_fields) {
256 if (rd->alloc_fields >= 32768)
258 if (rd->alloc_fields < 8)
259 rd->alloc_fields = 8;
261 rd->alloc_fields *= 2;
262 rd->fields = realloc(rd->fields,
263 sizeof(*rd->fields) * rd->alloc_fields);
265 rf = &rd->fields[rd->nfields++];
266 memset(rf, 0, sizeof(*rf));
267 rf->attr_start = rd->nfields; /* i.e. no attr ends here */
269 rf->attr_depth = rd->fields[rd->nfields-2].attr_depth;
273 static char *rf_add_field(struct rf_data *rd safe, char *str safe)
275 struct rf_field *rf = new_field(rd);
280 if (str[0] == '<' && str[1] == '/' && str[2] == '>') {
283 for (start = rd->nfields-2 ; start >= 0; start -= 1)
284 if (rd->fields[start].attr &&
285 rd->fields[start].attr_end == 0)
288 rd->fields[start].attr_end = rd->nfields-1;
289 rf->attr_start = start;
293 if (str[0] == '<' && str[1] != '<' && (str[1] != '/' || str[2] != '>')) {
296 while (str[0] && str[0] != '>')
301 if (str[0] == '<' && str[1] != '<')
302 /* More attr start/stop, must go in next field */
305 if (str[0] != '%' || str[1] == '%') {
306 /* Must be literal */
308 if (str[0] == '<' || str[0] == '%') {
309 /* must be '<<' or '%%', only include second
315 while (str[0] && str[0] != '<' && str[0] != '%') {
316 get_utf8((const char **)&str, NULL);
321 /* This is a '%' field */
326 while (*str == '-' || *str == '_' || isalnum(*str))
328 rf->val_len = str - rf->val;
336 while (isdigit(*str)) {
338 rf->width += *str - '0';
344 static void set_format(struct pane *focus safe, struct rf_data *rd safe)
351 str = pane_attr_get(focus, "line-format");
352 rd->format = strdup(str ?: "%name");
355 str = rf_add_field(rd, str);
357 for (f = rd->nfields - 1; f >= 0; f--) {
358 struct rf_field *rf = &rd->fields[f];
360 if (rf->attr && rf->attr_end == 0)
361 rf_add_field(rd, "</>");
365 static int field_size(struct pane *home safe, struct pane *focus safe,
366 struct mark *m safe, int field,
367 const char **valp safe)
369 struct rf_data *rd = home->data;
374 if (field < 0 || field > rd->nfields)
376 if (field == rd->nfields) {
377 /* Just a newline at the end */
381 rf = &rd->fields[field];
387 else if (rd->attr_cache && rd->cache_field == field &&
388 rd->cache_pos == m->ref.p) {
389 val = rd->attr_cache;
390 *valp = strsave(home, val);
393 strncpy(b, rf->val, 80);
395 if (rf->val_len < 80)
397 val = pane_mark_attr(focus, m, b);
402 free(rd->attr_cache);
403 rd->attr_cache = strdup(val);
404 rd->cache_field = field;
405 rd->cache_pos = m->ref.p;
407 l = utf8_strlen(val);
414 static int normalize(struct pane *home safe, struct pane *focus safe,
415 struct mark *m safe, int inc)
417 struct rf_data *rd = home->data;
418 int index = m->ref.i;
419 unsigned short f = FIELD_NUM(index);
420 unsigned short o = FIELD_OFFSET(index);
423 const char *val = NULL;
426 len = field_size(home, focus, m, f, &val);
441 /* Try previous field */
453 if (f >= rd->nfields)
462 if (f >= rd->nfields)
471 return MAKE_INDEX(f, o);
474 static void update_offset(struct mark *m safe, struct rf_data *rd safe,
478 struct mark *target = m;
483 /* If o is the first visible field, it needs to be 0 */
485 for (f = 0; f < rd->nfields; f++)
486 if (rd->fields[f].var ||
487 rd->fields[f].val_len)
489 if (o <= MAKE_INDEX(f, 0))
496 while (m2 && m2->ref.p == m->ref.p && m2->ref.i <= o) {
501 while (m2 && m2->ref.p == m->ref.p && m2->ref.i >= o) {
507 mark_to_mark_noref(m, target);
510 static void prev_line(struct pane *home safe, struct mark *m safe)
512 struct rf_data *rd = home->data;
514 /* Move m to end of previous line, just before the newline */
515 if (doc_prev(home->parent, m) == WEOF) {
516 /* At the start already */
517 update_offset(m, rd, 0);
520 update_offset(m, rd, MAKE_INDEX(rd->nfields, 0));
524 static void next_line(struct pane *home safe, struct mark *m safe)
526 struct rf_data *rd = home->data;
528 doc_next(home->parent, m);
529 update_offset(m, rd, MAKE_INDEX(0, 0));
533 static inline wint_t format_next_prev(struct pane *home safe, struct pane *focus safe,
534 struct mark *m safe, struct doc_ref *r safe,
535 int forward, bool bytes)
537 struct rf_data *rd = home->data;
539 int move = r == &m->ref;
544 const char *val = NULL;
547 set_format(focus, rd);
550 index = normalize(home, focus, m, -1);
552 if (doc_prior(home->parent, m) == WEOF)
553 return CHAR_RET(WEOF);
556 return CHAR_RET('\n');
559 if (m->ref.p == NULL)
560 return CHAR_RET(WEOF);
561 index = normalize(home, focus, m, 0);
563 /* Should be impossible */
564 return CHAR_RET(WEOF);
566 f = FIELD_NUM(index);
567 o = FIELD_OFFSET(index);
569 if (f >= rd->nfields) {
572 return CHAR_RET('\n');
575 fsize = field_size(home, focus, m, f, &val);
576 if (move && forward) {
577 index = normalize(home, focus, m, 1);
580 return CHAR_RET('\n');
582 update_offset(m, rd, index);
583 } else if (move && !forward) {
584 update_offset(m, rd, index);
589 while (o > 0 && get_utf8(&val, NULL) < WERR) {
591 if (val[-1] == '%' || val[-1] == '<')
594 return CHAR_RET(get_utf8(&val, NULL));
599 len = utf8_strlen(val);
608 margin = (fsize - len) / 2;
613 if (o >= margin + len)
618 margin = fsize - len;
626 while (o > 0 && get_utf8(&val, NULL) < WERR)
628 return CHAR_RET(get_utf8(&val, NULL));
633 return do_char_byte(ci);
636 DEF_CMD(format_content2)
638 /* doc:content delivers one char at a time to a callback.
639 * This is used e.g. for 'search' and 'copy'.
641 * .mark is 'location': to start. This is moved forwards
642 * .mark if set is a location to stop
643 * .comm2 is 'consume': pass char mark and report if finished.
646 struct pane *home = ci->home;
647 struct pane *focus = ci->focus;
648 struct rf_data *rd = home->data;
650 struct mark *m = ci->mark;
651 struct mark *end = ci->mark2;
653 int len, index, f, o, fsize, margin;
657 if (!m || !ci->comm2)
660 /* Cannot handle bytes */
662 set_format(focus, rd);
667 if (pane_too_long(home, 2000))
669 if (m->ref.p == NULL)
671 index = normalize(home, focus, m, 0);
675 f = FIELD_NUM(index);
676 o = FIELD_OFFSET(index);
678 if (f >= rd->nfields) {
685 fsize = field_size(home, focus, m, f, &val);
687 index = normalize(home, focus, m, 1);
693 update_offset(m, rd, index);
696 const char *vend = rf->val + rf->val_len;
700 while ((nxt = get_utf8(&val, vend)) < WERR) {
701 if (nxt == '%' || nxt == '<')
704 (!end || mark_ordered_or_same(m, end))) {
706 if (comm_call(ci->comm2,
711 update_offset(m, rd, MAKE_INDEX(f, i+1));
724 len = utf8_strlen(val);
731 margin = (fsize - len) / 2;
736 margin = fsize - len;
742 for (i = 0; i < fsize; i++) {
743 if ((rf->align == 'c' &&
744 (i < margin || i >= margin + len)) ||
745 (rf->align == 'r' && i < margin) ||
746 (rf->align != 'c' && rf->align != 'r' &&
750 nxt = get_utf8(&val, NULL);
753 if (comm_call(ci->comm2,
758 update_offset(m, rd, MAKE_INDEX(f, i+1));
763 } while (nxt > 0 && nxt != WEOF &&
764 (!end || mark_ordered_or_same(m, end)) &&
765 comm_call(ci->comm2, "consume", ci->focus, nxt, m) > 0);
773 /* If there are attrs here, we report that by returning
774 * "render:format" as "yes". This causes map-attr to called so
775 * that it can insert those attrs.
777 * Also "format:plain" which formats the line directly
778 * without the cost of all the lib-markup machinery.
780 struct rf_data *rd = ci->home->data;
781 struct mark *m = ci->mark;
785 bool need_attr = False;
793 if (strcmp(ci->str, "format:plain") == 0) {
794 char *v = do_format(ci->focus, m, NULL, -1, 0);
796 comm_call(ci->comm2, "", ci->focus, 0, m, v);
800 if (ci->num2 == 0 && strcmp(ci->str, "render:format") != 0)
802 if (ci->num2 && strncmp(ci->str, "render:format", strlen(ci->str)) != 0)
806 /* idx of 0 is special and may not be normalized */
808 idx = normalize(ci->home, ci->focus, m, 0);
809 if (FIELD_OFFSET(idx) > 0)
810 /* attribute changes only happen at start of a field */
813 /* There may be several previous fields that are empty.
814 * We need consider the possibility that any of those
815 * change the attributes.
817 previ = normalize(ci->home, ci->focus, m, -1);
821 f0 = FIELD_NUM(previ)+1;
822 for(f = f0; f <= FIELD_NUM(idx); f++) {
823 if (f < rd->nfields) {
824 if (rd->fields[f].attr_end > FIELD_NUM(idx) ||
825 rd->fields[f].attr_start < f0)
830 if (strcmp(ci->str, "render:format") == 0)
831 comm_call(ci->comm2, "", ci->focus, 0, m, "yes");
833 comm_call(ci->comm2, "", ci->focus, 0, m, "yes",
834 0, NULL, "render:format");
841 struct rf_data *rd = ci->home->data;
842 struct mark *m = ci->mark;
848 if (strcmp(ci->str, "render:format") != 0)
850 if (m->ref.p == NULL)
854 idx = normalize(ci->home, ci->focus, m, 0);
855 if (FIELD_OFFSET(idx) > 0)
856 /* attribute changes only happen at start of a field */
860 /* There may be several previous fields that are empty.
861 * We need to consider the possibility that any of those
862 * change the attributes.
864 previ = normalize(ci->home, ci->focus, m, -1);
868 f0 = FIELD_NUM(previ)+1;
869 for(f = f0; f <= FIELD_NUM(idx); f++) {
870 if (f >= rd->nfields || !rd->fields)
872 /* Each depth gets a priority level from 0 up.
873 * When starting, set length to v.large. When ending, set
876 if (rd->fields[f].attr_start < f0) {
877 struct rf_field *st =
878 &rd->fields[rd->fields[f].attr_start];
879 comm_call(ci->comm2, "", ci->focus, -1, m,
880 NULL, st->attr_depth);
882 if (rd->fields[f].attr_end > FIELD_NUM(idx)) {
883 struct rf_field *st = &rd->fields[f];
884 const char *attr = st->attr;
885 if (attr && attr[0] == '%')
886 attr = pane_mark_attr(ci->focus, m, attr+1);
887 comm_call(ci->comm2, "", ci->focus, 0, m,
888 attr, st->attr_depth);
894 DEF_CMD(render_line_prev2)
896 struct rf_data *rd = ci->home->data;
897 struct mark *m = ci->mark;
898 struct mark *m2, *mn;
902 if (RPT_NUM(ci) == 0)
904 else if (doc_prev(ci->home->parent, m) == WEOF)
905 /* Hit start-of-file */
908 while ((mn = mark_prev(m2)) != NULL &&
909 mn->ref.p == m2->ref.p &&
913 update_offset(m, rd, 0);
918 static struct pane *do_render_format_attach(struct pane *parent safe);
919 DEF_CMD(format_clone)
923 p = do_render_format_attach(ci->focus);
924 pane_clone_children(ci->home, p);
928 DEF_CMD(format_noshare_ref)
933 static struct map *rf_map, *rf2_map;
935 static void render_format_register_map(void)
937 rf_map = key_alloc();
939 key_add(rf_map, "doc:render-line", &render_line);
940 key_add(rf_map, "doc:render-line-prev", &render_line_prev);
941 key_add(rf_map, "Clone", &format_clone);
942 key_add(rf_map, "doc:content", &format_content);
944 rf2_map = key_alloc();
946 key_add(rf2_map, "doc:char", &format_char);
947 key_add(rf2_map, "doc:get-attr", &format_attr);
948 key_add(rf2_map, "map-attr", &format_map);
949 key_add(rf2_map, "doc:render-line-prev", &render_line_prev2);
950 key_add(rf2_map, "Clone", &format_clone);
951 key_add(rf2_map, "doc:content", &format_content2);
952 key_add(rf2_map, "Close", &format_close);
953 key_add(rf2_map, "doc:shares-ref", &format_noshare_ref);
956 DEF_LOOKUP_CMD(render_format_handle, rf_map);
957 DEF_LOOKUP_CMD(render_format2_handle, rf2_map);
959 static struct pane *do_render_format_attach(struct pane *parent safe)
963 if (call("doc:shares-ref", parent) != 1) {
965 render_format_register_map();
967 p = pane_register_2(parent, 0, &render_format_handle.c);
970 render_format_register_map();
972 p = pane_register(parent, 0, &render_format2_handle.c);
976 if (!pane_attr_get(parent, "format:no-linecount")) {
977 struct pane *p2 = call_ret(pane, "attach-line-count", p);
984 attr_set_str(&p->attrs, "render-wrap", "no");
988 DEF_CMD(render_format_attach)
992 p = do_render_format_attach(ci->focus);
995 if (p->handle == &render_format_handle.c)
996 p = call_ret(pane, "attach-render-lines", p);
998 p = call_ret(pane, "attach-render-text", p);
1001 return comm_call(ci->comm2, "callback:attach", p);
1004 void edlib_init(struct pane *ed safe)
1006 call_comm("global-set-command", ed, &render_format_attach, 0, NULL, "attach-render-format");