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 DOC_NEXT(p,m,r,b) format_next_prev(p, ci->focus, m, r, 1, b)
19 #define DOC_PREV(p,m,r,b) format_next_prev(p, ci->focus, m, r, 0, b)
20 #define DOC_NEXT_DECL(p,m,r,b) format_next_prev(p, struct pane *focus safe, m, r, int forward, b)
21 #define DOC_PREV_DECL(p,m,r,b) format_next_prev(p, struct pane *focus safe, m, r, int forward, b)
27 unsigned short nfields;
28 unsigned short alloc_fields;
30 /* 'field' can end at most one attribute, start at most one,
31 * can contain one text source, either var or literal.
33 const char *val safe; /* pointer into 'format' */
34 const char *attr; /* pointer into 'format', or NULL */
35 unsigned short attr_end;/* field where this attr ends */
36 unsigned short attr_start;/* starting field for attr which ends here */
37 unsigned short val_len; /* length in 'format' */
38 unsigned short attr_depth;
39 short width; /* min display width */
40 unsigned short min_attr_depth; /* attr depth of first attr - from 0 */
41 bool var; /* else constant */
42 char align; /* l,r,c */
49 #include "core-pane.h"
51 static inline short FIELD_NUM(int i) { return i >> 16; }
52 static inline short FIELD_OFFSET(int i) { return i & 0xFFFF; }
53 static inline unsigned int MAKE_INDEX(short f, short i) { return (int)f << 16 | i;}
55 static char *do_format(struct pane *focus safe,
56 struct mark *m safe, struct mark *pm,
59 char *body = pane_attr_get(focus, "line-format");
63 if (pm && !mark_same(pm, m))
72 if (len >= 0 && ret.len >= len)
76 char buf[40], *b, *val;
79 if (!attrs && *n == '<' && n[1] != '<') {
80 /* an attribute, skip it */
82 while (*n && *n != '>')
88 if (*n != '%' || n[1] == '%') {
89 buf_append_byte(&ret, *n);
96 if (len >= 0 && ret.len >= len)
102 while (*n == '-' || *n == '_' || isalnum(*n)) {
103 if (b < buf + sizeof(buf) - 2)
111 val = pane_mark_attr(focus, m, buf);
117 if (*val == '<' && attrs)
118 buf_append_byte(&ret, '<');
119 buf_append_byte(&ret, *val);
129 w = w * 10 + (*n - '0');
130 else if (w == 0 && *n == '-')
136 while (adjust && w > l) {
137 buf_append(&ret, ' ');
141 while (*val && w > 0 ) {
142 if (*val == '<' && attrs)
143 buf_append_byte(&ret, '<');
144 buf_append_byte(&ret, *val);
149 buf_append(&ret, ' ');
156 buf_append(&ret, '\n');
158 return buf_final(&ret);
161 DEF_CMD(format_content)
165 if (!ci->mark || !ci->comm2)
168 /* Cannot handle bytes */
171 m = mark_dup(ci->mark);
172 while (doc_following(ci->focus, m) != WEOF) {
176 l = do_format(ci->focus, m, NULL, -1, 0);
179 doc_next(ci->focus, m);
182 w = get_utf8(&c, NULL);
184 comm_call(ci->comm2, "consume", ci->focus, w, m) <= 0)
198 struct mark *m = ci->mark;
199 struct mark *pm = ci->mark2;
206 if (doc_following(ci->focus, ci->mark) == WEOF)
209 if (pm && !mark_same(pm, m))
215 ret = do_format(ci->focus, ci->mark, pm, len, 1);
217 doc_next(ci->focus, m);
218 rv = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, ret);
223 DEF_CMD(render_line_prev)
225 struct mark *m = ci->mark;
229 if (RPT_NUM(ci) == 0)
230 /* always at start-of-line */
232 if (doc_prev(ci->focus, m) == WEOF)
233 /* Hit start-of-file */
238 DEF_CMD_CLOSED(format_close)
240 struct rf_data *rf = ci->home->data;
242 free(rf->attr_cache); rf->attr_cache = NULL;
243 free(rf->fields); rf->fields = NULL;
244 free(rf->format); rf->format = "";
249 static struct rf_field *new_field(struct rf_data *rd safe)
253 if (rd->nfields >= rd->alloc_fields) {
254 if (rd->alloc_fields >= 32768)
256 if (rd->alloc_fields < 8)
257 rd->alloc_fields = 8;
259 rd->alloc_fields *= 2;
260 rd->fields = realloc(rd->fields,
261 sizeof(*rd->fields) * rd->alloc_fields);
263 rf = &rd->fields[rd->nfields++];
264 memset(rf, 0, sizeof(*rf));
265 rf->attr_start = rd->nfields; /* i.e. no attr ends here */
267 rf->attr_depth = rd->fields[rd->nfields-2].attr_depth;
271 static char *rf_add_field(struct rf_data *rd safe, char *str safe)
273 struct rf_field *rf = new_field(rd);
278 if (str[0] == '<' && str[1] == '/' && str[2] == '>') {
281 for (start = rd->nfields-2 ; start >= 0; start -= 1)
282 if (rd->fields[start].attr &&
283 rd->fields[start].attr_end == 0)
286 rd->fields[start].attr_end = rd->nfields-1;
287 rf->attr_start = start;
291 if (str[0] == '<' && str[1] != '<' && (str[1] != '/' || str[2] != '>')) {
294 while (str[0] && str[0] != '>')
299 if (str[0] == '<' && str[1] != '<')
300 /* More attr start/stop, must go in next field */
303 if (str[0] != '%' || str[1] == '%') {
304 /* Must be literal */
306 if (str[0] == '<' || str[0] == '%') {
307 /* must be '<<' or '%%', only include second
313 while (str[0] && str[0] != '<' && str[0] != '%') {
314 get_utf8((const char **)&str, NULL);
319 /* This is a '%' field */
324 while (*str == '-' || *str == '_' || isalnum(*str))
326 rf->val_len = str - rf->val;
334 while (isdigit(*str)) {
336 rf->width += *str - '0';
342 static void set_format(struct pane *focus safe, struct rf_data *rd safe)
349 str = pane_attr_get(focus, "line-format");
350 rd->format = strdup(str ?: "%name");
353 str = rf_add_field(rd, str);
355 for (f = rd->nfields - 1; f >= 0; f--) {
356 struct rf_field *rf = &rd->fields[f];
358 if (rf->attr && rf->attr_end == 0)
359 rf_add_field(rd, "</>");
363 static int field_size(struct pane *home safe, struct pane *focus safe,
364 struct mark *m safe, int field,
365 const char **valp safe)
367 struct rf_data *rd = home->data;
372 if (field < 0 || field > rd->nfields)
374 if (field == rd->nfields) {
375 /* Just a newline at the end */
379 rf = &rd->fields[field];
385 else if (rd->attr_cache && rd->cache_field == field &&
386 rd->cache_pos == m->ref.p) {
387 val = rd->attr_cache;
388 *valp = strsave(home, val);
391 strncpy(b, rf->val, 80);
393 if (rf->val_len < 80)
395 val = pane_mark_attr(focus, m, b);
400 free(rd->attr_cache);
401 rd->attr_cache = strdup(val);
402 rd->cache_field = field;
403 rd->cache_pos = m->ref.p;
405 l = utf8_strlen(val);
412 static int normalize(struct pane *home safe, struct pane *focus safe,
413 struct mark *m safe, int inc)
415 struct rf_data *rd = home->data;
416 int index = m->ref.i;
417 unsigned short f = FIELD_NUM(index);
418 unsigned short o = FIELD_OFFSET(index);
421 const char *val = NULL;
424 len = field_size(home, focus, m, f, &val);
439 /* Try previous field */
451 if (f >= rd->nfields)
460 if (f >= rd->nfields)
469 return MAKE_INDEX(f, o);
472 static void update_offset(struct mark *m safe, struct rf_data *rd safe,
476 struct mark *target = m;
481 /* If o is the first visible field, it needs to be 0 */
483 for (f = 0; f < rd->nfields; f++)
484 if (rd->fields[f].var ||
485 rd->fields[f].val_len)
487 if (o <= MAKE_INDEX(f, 0))
494 while (m2 && m2->ref.p == m->ref.p && m2->ref.i <= o) {
499 while (m2 && m2->ref.p == m->ref.p && m2->ref.i >= o) {
505 mark_to_mark_noref(m, target);
508 static void prev_line(struct pane *home safe, struct mark *m safe)
510 struct rf_data *rd = home->data;
512 /* Move m to end of previous line, just before the newline */
513 if (doc_prev(home->parent, m) == WEOF) {
514 /* At the start already */
515 update_offset(m, rd, 0);
518 update_offset(m, rd, MAKE_INDEX(rd->nfields, 0));
522 static void next_line(struct pane *home safe, struct mark *m safe)
524 struct rf_data *rd = home->data;
526 doc_next(home->parent, m);
527 update_offset(m, rd, MAKE_INDEX(0, 0));
531 static inline wint_t format_next_prev(struct pane *home safe, struct pane *focus safe,
532 struct mark *m safe, struct doc_ref *r safe,
533 int forward, bool bytes)
535 struct rf_data *rd = home->data;
537 int move = r == &m->ref;
542 const char *val = NULL;
545 set_format(focus, rd);
548 index = normalize(home, focus, m, -1);
550 if (doc_prior(home->parent, m) == WEOF)
551 return CHAR_RET(WEOF);
554 return CHAR_RET('\n');
557 if (m->ref.p == NULL)
558 return CHAR_RET(WEOF);
559 index = normalize(home, focus, m, 0);
561 /* Should be impossible */
562 return CHAR_RET(WEOF);
564 f = FIELD_NUM(index);
565 o = FIELD_OFFSET(index);
567 if (f >= rd->nfields) {
570 return CHAR_RET('\n');
573 fsize = field_size(home, focus, m, f, &val);
574 if (move && forward) {
575 index = normalize(home, focus, m, 1);
578 return CHAR_RET('\n');
580 update_offset(m, rd, index);
581 } else if (move && !forward) {
582 update_offset(m, rd, index);
587 while (o > 0 && get_utf8(&val, NULL) < WERR) {
589 if (val[-1] == '%' || val[-1] == '<')
592 return CHAR_RET(get_utf8(&val, NULL));
597 len = utf8_strlen(val);
606 margin = (fsize - len) / 2;
611 if (o >= margin + len)
616 margin = fsize - len;
624 while (o > 0 && get_utf8(&val, NULL) < WERR)
626 return CHAR_RET(get_utf8(&val, NULL));
631 return do_char_byte(ci);
634 DEF_CMD(format_content2)
636 /* doc:content delivers one char at a time to a callback.
637 * This is used e.g. for 'search' and 'copy'.
639 * .mark is 'location': to start. This is moved forwards
640 * .mark if set is a location to stop
641 * .comm2 is 'consume': pass char mark and report if finished.
644 struct pane *home = ci->home;
645 struct pane *focus = ci->focus;
646 struct rf_data *rd = home->data;
648 struct mark *m = ci->mark;
649 struct mark *end = ci->mark2;
651 int len, index, f, o, fsize, margin;
655 if (!m || !ci->comm2)
658 /* Cannot handle bytes */
660 set_format(focus, rd);
665 if (pane_too_long(home, 2000))
667 if (m->ref.p == NULL)
669 index = normalize(home, focus, m, 0);
673 f = FIELD_NUM(index);
674 o = FIELD_OFFSET(index);
676 if (f >= rd->nfields) {
683 fsize = field_size(home, focus, m, f, &val);
685 index = normalize(home, focus, m, 1);
691 update_offset(m, rd, index);
694 const char *vend = rf->val + rf->val_len;
698 while ((nxt = get_utf8(&val, vend)) < WERR) {
699 if (nxt == '%' || nxt == '<')
702 (!end || mark_ordered_or_same(m, end))) {
704 if (comm_call(ci->comm2,
709 update_offset(m, rd, MAKE_INDEX(f, i+1));
722 len = utf8_strlen(val);
729 margin = (fsize - len) / 2;
734 margin = fsize - len;
740 for (i = 0; i < fsize; i++) {
741 if ((rf->align == 'c' &&
742 (i < margin || i >= margin + len)) ||
743 (rf->align == 'r' && i < margin) ||
744 (rf->align != 'c' && rf->align != 'r' &&
748 nxt = get_utf8(&val, NULL);
751 if (comm_call(ci->comm2,
756 update_offset(m, rd, MAKE_INDEX(f, i+1));
761 } while (nxt > 0 && nxt != WEOF &&
762 (!end || mark_ordered_or_same(m, end)) &&
763 comm_call(ci->comm2, "consume", ci->focus, nxt, m) > 0);
771 /* If there are attrs here, we report that by returning
772 * "render:format" as "yes". This causes map-attr to called so
773 * that it can insert those attrs.
775 * Also "format:plain" which formats the line directly
776 * without the cost of all the lib-markup machinery.
778 struct rf_data *rd = ci->home->data;
779 struct mark *m = ci->mark;
783 bool need_attr = False;
791 if (strcmp(ci->str, "format:plain") == 0) {
792 char *v = do_format(ci->focus, m, NULL, -1, 0);
794 comm_call(ci->comm2, "", ci->focus, 0, m, v);
798 if (ci->num2 == 0 && strcmp(ci->str, "render:format") != 0)
800 if (ci->num2 && strncmp(ci->str, "render:format", strlen(ci->str)) != 0)
804 /* idx of 0 is special and may not be normalized */
806 idx = normalize(ci->home, ci->focus, m, 0);
807 if (FIELD_OFFSET(idx) > 0)
808 /* attribute changes only happen at start of a field */
811 /* There may be several previous fields that are empty.
812 * We need consider the possibility that any of those
813 * change the attributes.
815 previ = normalize(ci->home, ci->focus, m, -1);
819 f0 = FIELD_NUM(previ)+1;
820 for(f = f0; f <= FIELD_NUM(idx); f++) {
821 if (f < rd->nfields) {
822 if (rd->fields[f].attr_end > FIELD_NUM(idx) ||
823 rd->fields[f].attr_start < f0)
828 if (strcmp(ci->str, "render:format") == 0)
829 comm_call(ci->comm2, "", ci->focus, 0, m, "yes");
831 comm_call(ci->comm2, "", ci->focus, 0, m, "yes",
832 0, NULL, "render:format");
839 struct rf_data *rd = ci->home->data;
840 struct mark *m = ci->mark;
846 if (strcmp(ci->str, "render:format") != 0)
848 if (m->ref.p == NULL)
852 idx = normalize(ci->home, ci->focus, m, 0);
853 if (FIELD_OFFSET(idx) > 0)
854 /* attribute changes only happen at start of a field */
858 /* There may be several previous fields that are empty.
859 * We need to consider the possibility that any of those
860 * change the attributes.
862 previ = normalize(ci->home, ci->focus, m, -1);
866 f0 = FIELD_NUM(previ)+1;
867 for(f = f0; f <= FIELD_NUM(idx); f++) {
868 if (f >= rd->nfields || !rd->fields)
870 /* Each depth gets a priority level from 0 up.
871 * When starting, set length to v.large. When ending, set
874 if (rd->fields[f].attr_start < f0) {
875 struct rf_field *st =
876 &rd->fields[rd->fields[f].attr_start];
877 comm_call(ci->comm2, "", ci->focus, -1, m,
878 NULL, st->attr_depth);
880 if (rd->fields[f].attr_end > FIELD_NUM(idx)) {
881 struct rf_field *st = &rd->fields[f];
882 const char *attr = st->attr;
883 if (attr && attr[0] == '%')
884 attr = pane_mark_attr(ci->focus, m, attr+1);
885 comm_call(ci->comm2, "", ci->focus, 0, m,
886 attr, st->attr_depth);
892 DEF_CMD(render_line_prev2)
894 struct rf_data *rd = ci->home->data;
895 struct mark *m = ci->mark;
896 struct mark *m2, *mn;
900 if (RPT_NUM(ci) == 0)
902 else if (doc_prev(ci->home->parent, m) == WEOF)
903 /* Hit start-of-file */
906 while ((mn = mark_prev(m2)) != NULL &&
907 mn->ref.p == m2->ref.p &&
911 update_offset(m, rd, 0);
916 static struct pane *do_render_format_attach(struct pane *parent safe);
917 DEF_CMD(format_clone)
921 p = do_render_format_attach(ci->focus);
922 pane_clone_children(ci->home, p);
926 DEF_CMD(format_noshare_ref)
931 static struct map *rf_map, *rf2_map;
933 static void render_format_register_map(void)
935 rf_map = key_alloc();
937 key_add(rf_map, "doc:render-line", &render_line);
938 key_add(rf_map, "doc:render-line-prev", &render_line_prev);
939 key_add(rf_map, "Clone", &format_clone);
940 key_add(rf_map, "doc:content", &format_content);
942 rf2_map = key_alloc();
944 key_add(rf2_map, "doc:char", &format_char);
945 key_add(rf2_map, "doc:get-attr", &format_attr);
946 key_add(rf2_map, "map-attr", &format_map);
947 key_add(rf2_map, "doc:render-line-prev", &render_line_prev2);
948 key_add(rf2_map, "Clone", &format_clone);
949 key_add(rf2_map, "doc:content", &format_content2);
950 key_add(rf2_map, "Close", &format_close);
951 key_add(rf2_map, "doc:shares-ref", &format_noshare_ref);
954 DEF_LOOKUP_CMD(render_format_handle, rf_map);
955 DEF_LOOKUP_CMD(render_format2_handle, rf2_map);
957 static struct pane *do_render_format_attach(struct pane *parent safe)
961 if (call("doc:shares-ref", parent) != 1) {
963 render_format_register_map();
965 p = pane_register(parent, 0, &render_format_handle.c, NULL);
968 render_format_register_map();
970 p = pane_register(parent, 0, &render_format2_handle.c);
974 if (!pane_attr_get(parent, "format:no-linecount")) {
975 struct pane *p2 = call_ret(pane, "attach-line-count", p);
982 attr_set_str(&p->attrs, "render-wrap", "no");
986 DEF_CMD(render_format_attach)
990 p = do_render_format_attach(ci->focus);
993 if (p->handle == &render_format_handle.c)
994 p = call_ret(pane, "attach-render-lines", p);
996 p = call_ret(pane, "attach-render-text", p);
999 return comm_call(ci->comm2, "callback:attach", p);
1002 void edlib_init(struct pane *ed safe)
1004 call_comm("global-set-command", ed, &render_format_attach, 0, NULL, "attach-render-format");