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 */
240 struct rf_data *rf = ci->home->data;
242 free(rf->attr_cache);
248 static struct rf_field *new_field(struct rf_data *rd safe)
252 if (rd->nfields >= rd->alloc_fields) {
253 if (rd->alloc_fields >= 32768)
255 if (rd->alloc_fields < 8)
256 rd->alloc_fields = 8;
258 rd->alloc_fields *= 2;
259 rd->fields = realloc(rd->fields,
260 sizeof(*rd->fields) * rd->alloc_fields);
262 rf = &rd->fields[rd->nfields++];
263 memset(rf, 0, sizeof(*rf));
264 rf->attr_start = rd->nfields; /* i.e. no attr ends here */
266 rf->attr_depth = rd->fields[rd->nfields-2].attr_depth;
270 static char *rf_add_field(struct rf_data *rd safe, char *str safe)
272 struct rf_field *rf = new_field(rd);
277 if (str[0] == '<' && str[1] == '/' && str[2] == '>') {
280 for (start = rd->nfields-2 ; start >= 0; start -= 1)
281 if (rd->fields[start].attr &&
282 rd->fields[start].attr_end == 0)
285 rd->fields[start].attr_end = rd->nfields-1;
286 rf->attr_start = start;
290 if (str[0] == '<' && str[1] != '<' && (str[1] != '/' || str[2] != '>')) {
293 while (str[0] && str[0] != '>')
298 if (str[0] == '<' && str[1] != '<')
299 /* More attr start/stop, must go in next field */
302 if (str[0] != '%' || str[1] == '%') {
303 /* Must be literal */
305 if (str[0] == '<' || str[0] == '%') {
306 /* must be '<<' or '%%', only include second
312 while (str[0] && str[0] != '<' && str[0] != '%') {
313 get_utf8((const char **)&str, NULL);
318 /* This is a '%' field */
323 while (*str == '-' || *str == '_' || isalnum(*str))
325 rf->val_len = str - rf->val;
333 while (isdigit(*str)) {
335 rf->width += *str - '0';
341 static void set_format(struct pane *focus safe, struct rf_data *rd safe)
348 str = pane_attr_get(focus, "line-format");
349 rd->format = strdup(str ?: "%name");
352 str = rf_add_field(rd, str);
354 for (f = rd->nfields - 1; f >= 0; f--) {
355 struct rf_field *rf = &rd->fields[f];
357 if (rf->attr && rf->attr_end == 0)
358 rf_add_field(rd, "</>");
362 static int field_size(struct pane *home safe, struct pane *focus safe,
363 struct mark *m safe, int field,
364 const char **valp safe)
366 struct rf_data *rd = home->data;
371 if (field < 0 || field > rd->nfields)
373 if (field == rd->nfields) {
374 /* Just a newline at the end */
378 rf = &rd->fields[field];
384 else if (rd->attr_cache && rd->cache_field == field &&
385 rd->cache_pos == m->ref.p) {
386 val = rd->attr_cache;
387 *valp = strsave(home, val);
390 strncpy(b, rf->val, 80);
392 if (rf->val_len < 80)
394 val = pane_mark_attr(focus, m, b);
399 free(rd->attr_cache);
400 rd->attr_cache = strdup(val);
401 rd->cache_field = field;
402 rd->cache_pos = m->ref.p;
404 l = utf8_strlen(val);
411 static int normalize(struct pane *home safe, struct pane *focus safe,
412 struct mark *m safe, int inc)
414 struct rf_data *rd = home->data;
415 int index = m->ref.i;
416 unsigned short f = FIELD_NUM(index);
417 unsigned short o = FIELD_OFFSET(index);
420 const char *val = NULL;
423 len = field_size(home, focus, m, f, &val);
438 /* Try previous field */
450 if (f >= rd->nfields)
459 if (f >= rd->nfields)
468 return MAKE_INDEX(f, o);
471 static void update_offset(struct mark *m safe, struct rf_data *rd safe,
475 struct mark *target = m;
478 /* If o is the first visible field, it needs to be 0 */
480 for (f = 0; f < rd->nfields; f++)
481 if (rd->fields[f].var ||
482 rd->fields[f].val_len)
484 if (o <= MAKE_INDEX(f, 0))
491 while (m2 && m2->ref.p == m->ref.p && m2->ref.i <= o) {
496 while (m2 && m2->ref.p == m->ref.p && m2->ref.i >= o) {
502 mark_to_mark_noref(m, target);
505 static void prev_line(struct pane *home safe, struct mark *m safe)
507 struct rf_data *rd = home->data;
509 /* Move m to end of previous line, just before the newline */
510 if (doc_prev(home->parent, m) == WEOF) {
511 /* At the start already */
512 update_offset(m, rd, 0);
515 update_offset(m, rd, MAKE_INDEX(rd->nfields, 0));
519 static void next_line(struct pane *home safe, struct mark *m safe)
521 struct rf_data *rd = home->data;
523 doc_next(home->parent, m);
524 update_offset(m, rd, MAKE_INDEX(0, 0));
528 static inline wint_t format_next_prev(struct pane *home safe, struct pane *focus safe,
529 struct mark *m safe, struct doc_ref *r safe,
530 int forward, bool bytes)
532 struct rf_data *rd = home->data;
534 int move = r == &m->ref;
539 const char *val = NULL;
542 set_format(focus, rd);
545 index = normalize(home, focus, m, -1);
547 if (doc_prior(home->parent, m) == WEOF)
548 return CHAR_RET(WEOF);
551 return CHAR_RET('\n');
554 if (m->ref.p == NULL)
555 return CHAR_RET(WEOF);
556 index = normalize(home, focus, m, 0);
558 /* Should be impossible */
559 return CHAR_RET(WEOF);
561 f = FIELD_NUM(index);
562 o = FIELD_OFFSET(index);
564 if (f >= rd->nfields) {
567 return CHAR_RET('\n');
570 fsize = field_size(home, focus, m, f, &val);
571 if (move && forward) {
572 index = normalize(home, focus, m, 1);
575 return CHAR_RET('\n');
577 update_offset(m, rd, index);
578 } else if (move && !forward) {
579 update_offset(m, rd, index);
584 while (o > 0 && get_utf8(&val, NULL) < WERR) {
586 if (val[-1] == '%' || val[-1] == '<')
589 return CHAR_RET(get_utf8(&val, NULL));
594 len = utf8_strlen(val);
603 margin = (fsize - len) / 2;
608 if (o >= margin + len)
613 margin = fsize - len;
621 while (o > 0 && get_utf8(&val, NULL) < WERR)
623 return CHAR_RET(get_utf8(&val, NULL));
628 return do_char_byte(ci);
631 DEF_CMD(format_content2)
633 /* doc:content delivers one char at a time to a callback.
634 * This is used e.g. for 'search' and 'copy'.
636 * .mark is 'location': to start. This is moved forwards
637 * .mark if set is a location to stop
638 * .comm2 is 'consume': pass char mark and report if finished.
641 struct pane *home = ci->home;
642 struct pane *focus = ci->focus;
643 struct rf_data *rd = home->data;
645 struct mark *m = ci->mark;
646 struct mark *end = ci->mark2;
648 int len, index, f, o, fsize, margin;
652 if (!m || !ci->comm2)
655 /* Cannot handle bytes */
657 set_format(focus, rd);
662 if (pane_too_long(home, 2000))
664 if (m->ref.p == NULL)
666 index = normalize(home, focus, m, 0);
670 f = FIELD_NUM(index);
671 o = FIELD_OFFSET(index);
673 if (f >= rd->nfields) {
680 fsize = field_size(home, focus, m, f, &val);
682 index = normalize(home, focus, m, 1);
688 update_offset(m, rd, index);
691 const char *vend = rf->val + rf->val_len;
695 while ((nxt = get_utf8(&val, vend)) < WERR) {
696 if (nxt == '%' || nxt == '<')
699 (!end || mark_ordered_or_same(m, end))) {
701 if (comm_call(ci->comm2,
706 update_offset(m, rd, MAKE_INDEX(f, i+1));
719 len = utf8_strlen(val);
726 margin = (fsize - len) / 2;
731 margin = fsize - len;
737 for (i = 0; i < fsize; i++) {
738 if ((rf->align == 'c' &&
739 (i < margin || i >= margin + len)) ||
740 (rf->align == 'r' && i < margin) ||
741 (rf->align != 'c' && rf->align != 'r' &&
745 nxt = get_utf8(&val, NULL);
748 if (comm_call(ci->comm2,
753 update_offset(m, rd, MAKE_INDEX(f, i+1));
758 } while (nxt > 0 && nxt != WEOF &&
759 (!end || mark_ordered_or_same(m, end)) &&
760 comm_call(ci->comm2, "consume", ci->focus, nxt, m) > 0);
768 /* If there are attrs here, we report that by returning
769 * "render:format" as "yes". This causes map-attr to called so
770 * that it can insert those attrs.
772 * Also "format:plain" which formats the line directly
773 * without the cost of all the lib-markup machinery.
775 struct rf_data *rd = ci->home->data;
776 struct mark *m = ci->mark;
780 bool need_attr = False;
786 if (strcmp(ci->str, "format:plain") == 0) {
787 char *v = do_format(ci->focus, m, NULL, -1, 0);
789 comm_call(ci->comm2, "", ci->focus, 0, m, v);
793 if (ci->num2 == 0 && strcmp(ci->str, "render:format") != 0)
795 if (ci->num2 && strncmp(ci->str, "render:format", strlen(ci->str)) != 0)
799 /* idx of 0 is special and may not be normalized */
801 idx = normalize(ci->home, ci->focus, m, 0);
802 if (FIELD_OFFSET(idx) > 0)
803 /* attribute changes only happen at start of a field */
806 /* There may be several previous fields that are empty.
807 * We need consider the possibility that any of those
808 * change the attributes.
810 previ = normalize(ci->home, ci->focus, m, -1);
814 f0 = FIELD_NUM(previ)+1;
815 for(f = f0; f <= FIELD_NUM(idx); f++) {
816 if (f < rd->nfields) {
817 if (rd->fields[f].attr_end > FIELD_NUM(idx) ||
818 rd->fields[f].attr_start < f0)
823 if (strcmp(ci->str, "render:format") == 0)
824 comm_call(ci->comm2, "", ci->focus, 0, m, "yes");
826 comm_call(ci->comm2, "", ci->focus, 0, m, "yes",
827 0, NULL, "render:format");
834 struct rf_data *rd = ci->home->data;
835 struct mark *m = ci->mark;
841 if (strcmp(ci->str, "render:format") != 0)
843 if (m->ref.p == NULL)
847 idx = normalize(ci->home, ci->focus, m, 0);
848 if (FIELD_OFFSET(idx) > 0)
849 /* attribute changes only happen at start of a field */
853 /* There may be several previous fields that are empty.
854 * We need to consider the possibility that any of those
855 * change the attributes.
857 previ = normalize(ci->home, ci->focus, m, -1);
861 f0 = FIELD_NUM(previ)+1;
862 for(f = f0; f <= FIELD_NUM(idx); f++) {
863 if (f >= rd->nfields)
865 /* Each depth gets a priority level from 0 up.
866 * When starting, set length to v.large. When ending, set
869 if (rd->fields[f].attr_start < f0) {
870 struct rf_field *st =
871 &rd->fields[rd->fields[f].attr_start];
872 comm_call(ci->comm2, "", ci->focus, -1, m,
873 NULL, st->attr_depth);
875 if (rd->fields[f].attr_end > FIELD_NUM(idx)) {
876 struct rf_field *st = &rd->fields[f];
877 const char *attr = st->attr;
878 if (attr && attr[0] == '%')
879 attr = pane_mark_attr(ci->focus, m, attr+1);
880 comm_call(ci->comm2, "", ci->focus, 0, m,
881 attr, st->attr_depth);
887 DEF_CMD(render_line_prev2)
889 struct rf_data *rd = ci->home->data;
890 struct mark *m = ci->mark;
891 struct mark *m2, *mn;
895 if (RPT_NUM(ci) == 0)
897 else if (doc_prev(ci->home->parent, m) == WEOF)
898 /* Hit start-of-file */
901 while ((mn = mark_prev(m2)) != NULL &&
902 mn->ref.p == m2->ref.p &&
906 update_offset(m, rd, 0);
911 static struct pane *do_render_format_attach(struct pane *parent safe);
912 DEF_CMD(format_clone)
916 p = do_render_format_attach(ci->focus);
917 pane_clone_children(ci->home, p);
921 DEF_CMD(format_noshare_ref)
926 static struct map *rf_map, *rf2_map;
928 static void render_format_register_map(void)
930 rf_map = key_alloc();
932 key_add(rf_map, "doc:render-line", &render_line);
933 key_add(rf_map, "doc:render-line-prev", &render_line_prev);
934 key_add(rf_map, "Clone", &format_clone);
935 key_add(rf_map, "doc:content", &format_content);
937 rf2_map = key_alloc();
939 key_add(rf2_map, "doc:char", &format_char);
940 key_add(rf2_map, "doc:get-attr", &format_attr);
941 key_add(rf2_map, "map-attr", &format_map);
942 key_add(rf2_map, "doc:render-line-prev", &render_line_prev2);
943 key_add(rf2_map, "Clone", &format_clone);
944 key_add(rf2_map, "doc:content", &format_content2);
945 key_add(rf2_map, "Free", &format_free);
946 key_add(rf2_map, "doc:shares-ref", &format_noshare_ref);
949 DEF_LOOKUP_CMD(render_format_handle, rf_map);
950 DEF_LOOKUP_CMD(render_format2_handle, rf2_map);
952 static struct pane *do_render_format_attach(struct pane *parent safe)
956 if (call("doc:shares-ref", parent) != 1) {
958 render_format_register_map();
960 p = pane_register(parent, 0, &render_format_handle.c, NULL);
963 render_format_register_map();
965 p = pane_register(parent, 0, &render_format2_handle.c);
969 if (!pane_attr_get(parent, "format:no-linecount")) {
970 struct pane *p2 = call_ret(pane, "attach-line-count", p);
977 attr_set_str(&p->attrs, "render-wrap", "no");
981 DEF_CMD(render_format_attach)
985 p = do_render_format_attach(ci->focus);
988 if (p->handle == &render_format_handle.c)
989 p = call_ret(pane, "attach-render-lines", p);
991 p = call_ret(pane, "attach-render-text", p);
994 return comm_call(ci->comm2, "callback:attach", p);
997 void edlib_init(struct pane *ed safe)
999 call_comm("global-set-command", ed, &render_format_attach, 0, NULL, "attach-render-format");