]> git.neil.brown.name Git - edlib.git/blob - render-format.c
37308e164b145b7b6da11eec1cc03b349b5ae109
[edlib.git] / render-format.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
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.
8  *
9  * This is particularly used for directories and the document list.
10  */
11
12 #include <stdlib.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <stdio.h>
16
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)
22 #include "core.h"
23 #include "misc.h"
24
25 struct rf_data {
26         char *format;
27         unsigned short nfields;
28         unsigned short alloc_fields;
29         struct rf_field {
30                 /* 'field' can end at most one attribute, start at most one,
31                  * can contain one text source, either var or literal.
32                  */
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 */
43         } *fields safe;
44         char *attr_cache;
45         void *cache_pos;
46         int cache_field;
47 };
48
49 #include "core-pane.h"
50
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;}
54
55 static char *do_format(struct pane *focus safe,
56                        struct mark *m safe, struct mark *pm,
57                        int len, int attrs)
58 {
59         char *body = pane_attr_get(focus, "line-format");
60         struct buf ret;
61         char *n;
62
63         if (pm && !mark_same(pm, m))
64                 pm = NULL;
65         buf_init(&ret);
66
67         if (!body)
68                 body = "%name";
69         n = body;
70         if (pm)
71                 goto endwhile;
72         if (len >= 0 && ret.len >= len)
73                 goto endwhile;
74
75         while (*n) {
76                 char buf[40], *b, *val;
77                 int w, adjust, l;
78
79                 if (!attrs && *n == '<' && n[1] != '<') {
80                         /* an attribute, skip it */
81                         n += 1;
82                         while (*n && *n != '>')
83                                 n += 1;
84                         if (*n == '>')
85                                 n += 1;
86                         continue;
87                 }
88                 if (*n != '%' || n[1] == '%') {
89                         buf_append_byte(&ret, *n);
90                         if (*n == '%')
91                                 n += 1;
92                         n += 1;
93                         continue;
94                 }
95
96                 if (len >= 0 && ret.len >= len)
97                         break;
98                 if (pm)
99                         break;
100                 n += 1;
101                 b = buf;
102                 while (*n == '-' || *n == '_' || isalnum(*n)) {
103                         if (b < buf + sizeof(buf) - 2)
104                                 *b++ = *n;
105                         n += 1;
106                 }
107                 *b = 0;
108                 if (!buf[0])
109                         val = "";
110                 else
111                         val = pane_mark_attr(focus, m, buf);
112                 if (!val)
113                         val = "-";
114
115                 if (*n != ':') {
116                         while (*val) {
117                                 if (*val == '<' && attrs)
118                                         buf_append_byte(&ret, '<');
119                                 buf_append_byte(&ret, *val);
120                                 val += 1;
121                         }
122                         continue;
123                 }
124                 w = 0;
125                 adjust=0;
126                 n += 1;
127                 while (*n) {
128                         if (isdigit(*n))
129                                 w = w * 10 + (*n - '0');
130                         else if (w == 0 && *n == '-')
131                                 adjust = 1;
132                         else break;
133                         n += 1;
134                 }
135                 l = strlen(val);
136                 while (adjust && w > l) {
137                         buf_append(&ret, ' ');
138                         w -= 1;
139                 }
140
141                 while (*val && w > 0 ) {
142                         if (*val == '<' && attrs)
143                                 buf_append_byte(&ret, '<');
144                         buf_append_byte(&ret, *val);
145                         w -= 1;
146                         val += 1;
147                 }
148                 while (w > 0) {
149                         buf_append(&ret, ' ');
150                         w -= 1;
151                 }
152         }
153 endwhile:
154         if (!*n) {
155                 if (len < 0)
156                         buf_append(&ret, '\n');
157         }
158         return buf_final(&ret);
159 }
160
161 DEF_CMD(format_content)
162 {
163         struct mark *m;
164
165         if (!ci->mark || !ci->comm2)
166                 return Enoarg;
167         if (ci->num)
168                 /* Cannot handle bytes */
169                 return Einval;
170
171         m = mark_dup(ci->mark);
172         while (doc_following(ci->focus, m) != WEOF) {
173                 const char *l, *c;
174                 wint_t w;
175
176                 l = do_format(ci->focus, m, NULL, -1, 0);
177                 if (!l)
178                         break;
179                 doc_next(ci->focus, m);
180                 c = l;
181                 while (*c) {
182                         w = get_utf8(&c, NULL);
183                         if (w >= WERR ||
184                             comm_call(ci->comm2, "consume", ci->focus, w, m) <= 0)
185                                 /* Finished */
186                                 break;
187                 }
188                 free((void*)l);
189                 if (*c)
190                         break;
191         }
192         mark_free(m);
193         return 1;
194 }
195
196 DEF_CMD(render_line)
197 {
198         struct mark *m = ci->mark;
199         struct mark *pm = ci->mark2;
200         char *ret;
201         int rv;
202         int len;
203
204         if (!ci->mark)
205                 return Enoarg;
206         if (doc_following(ci->focus, ci->mark) == WEOF)
207                 return Efalse;
208
209         if (pm && !mark_same(pm, m))
210                 pm = NULL;
211         if (ci->num < 0)
212                 len = -1;
213         else
214                 len = ci->num;
215         ret = do_format(ci->focus, ci->mark, pm, len, 1);
216         if (len < 0)
217                 doc_next(ci->focus, m);
218         rv = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, ret);
219         free(ret);
220         return rv ?: 1;
221 }
222
223 DEF_CMD(render_line_prev)
224 {
225         struct mark *m = ci->mark;
226
227         if (!m)
228                 return Enoarg;
229         if (RPT_NUM(ci) == 0)
230                 /* always at start-of-line */
231                 return 1;
232         if (doc_prev(ci->focus, m) == WEOF)
233                 /* Hit start-of-file */
234                 return Efail;
235         return 1;
236 }
237
238 DEF_CMD(format_free)
239 {
240         struct rf_data *rf = ci->home->data;
241
242         free(rf->attr_cache);
243         free(rf->fields);
244         free(rf->format);
245         return 1;
246 }
247
248 static struct rf_field *new_field(struct rf_data *rd safe)
249 {
250         struct rf_field *rf;
251
252         if (rd->nfields >= rd->alloc_fields) {
253                 if (rd->alloc_fields >= 32768)
254                         return NULL;
255                 if (rd->alloc_fields < 8)
256                         rd->alloc_fields = 8;
257                 else
258                         rd->alloc_fields *= 2;
259                 rd->fields = realloc(rd->fields,
260                                      sizeof(*rd->fields) * rd->alloc_fields);
261         }
262         rf = &rd->fields[rd->nfields++];
263         memset(rf, 0, sizeof(*rf));
264         rf->attr_start = rd->nfields; /* i.e. no attr ends here */
265         if (rd->nfields > 1)
266                 rf->attr_depth = rd->fields[rd->nfields-2].attr_depth;
267         return rf;
268 }
269
270 static char *rf_add_field(struct rf_data *rd safe, char *str safe)
271 {
272         struct rf_field *rf = new_field(rd);
273
274         if (!rf)
275                 return NULL;
276
277         if (str[0] == '<' && str[1] == '/' && str[2] == '>') {
278                 int start;
279                 str += 3;
280                 for (start = rd->nfields-2 ; start >= 0; start -= 1)
281                         if (rd->fields[start].attr &&
282                             rd->fields[start].attr_end == 0)
283                                 break;
284                 if (start >= 0) {
285                         rd->fields[start].attr_end = rd->nfields-1;
286                         rf->attr_start = start;
287                         rf->attr_depth -= 1;
288                 }
289         }
290         if (str[0] == '<' && str[1] != '<' && (str[1] != '/' || str[2] != '>')) {
291                 rf->attr = str+1;
292                 rf->attr_depth += 1;
293                 while (str[0] && str[0] != '>')
294                         str++;
295                 if (str[0])
296                         *str++ = 0;
297         }
298         if (str[0] == '<' && str[1] != '<')
299                 /* More attr start/stop, must go in next field */
300                 return str;
301
302         if (str[0] != '%' || str[1] == '%') {
303                 /* Must be literal */
304                 rf->val = str;
305                 if (str[0] == '<' || str[0] == '%') {
306                         /* must be '<<' or '%%', only include second
307                          * in ->val */
308                         rf->val += 1;
309                         str += 2;
310                         rf->val_len = 1;
311                 }
312                 while (str[0] && str[0] != '<' && str[0] != '%') {
313                         get_utf8((const char **)&str, NULL);
314                         rf->val_len += 1;
315                 }
316                 return str;
317         }
318         /* This is a '%' field */
319         str += 1;
320         rf->val = str;
321         rf->align = 'l';
322         rf->var = True;
323         while (*str == '-' || *str == '_' || isalnum(*str))
324                 str += 1;
325         rf->val_len = str - rf->val;
326         if (*str != ':')
327                 return str;
328         str += 1;
329         if (*str == '-') {
330                 str += 1;
331                 rf->align = 'r';
332         }
333         while (isdigit(*str)) {
334                 rf->width *= 10;
335                 rf->width += *str - '0';
336                 str += 1;
337         }
338         return str;
339 }
340
341 static void set_format(struct pane *focus safe, struct rf_data *rd safe)
342 {
343         char *str;
344         int f;
345
346         if (rd->format)
347                 return;
348         str = pane_attr_get(focus, "line-format");
349         rd->format = strdup(str ?: "%name");
350         str = rd->format;
351         while (str && *str)
352                 str = rf_add_field(rd, str);
353
354         for (f = rd->nfields - 1; f >= 0; f--) {
355                 struct rf_field *rf = &rd->fields[f];
356
357                 if (rf->attr && rf->attr_end == 0)
358                         rf_add_field(rd, "</>");
359         }
360 }
361
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)
365 {
366         struct rf_data *rd = home->data;
367         struct rf_field *rf;
368         const char *val;
369         int l;
370
371         if (field < 0 || field > rd->nfields)
372                 return 0;
373         if (field == rd->nfields) {
374                 /* Just a newline at the end */
375                 *valp = "\n";
376                 return 1;
377         }
378         rf = &rd->fields[field];
379         if (!rf->var)
380                 return rf->val_len;
381         val = *valp;
382         if (val)
383                 ;
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);
388         } else {
389                 char b[80];
390                 strncpy(b, rf->val, 80);
391                 b[79] = 0;
392                 if (rf->val_len < 80)
393                         b[rf->val_len] = 0;
394                 val = pane_mark_attr(focus, m, b);
395                 if (!val)
396                         val = "-";
397                 *valp = val;
398                 if (rd->attr_cache)
399                         free(rd->attr_cache);
400                 rd->attr_cache = strdup(val);
401                 rd->cache_field = field;
402                 rd->cache_pos = m->ref.p;
403         }
404         l = utf8_strlen(val);
405         if (l < rf->width)
406                 return rf->width;
407         else
408                 return l;
409 }
410
411 static int normalize(struct pane *home safe, struct pane *focus safe,
412                      struct mark *m safe, int inc)
413 {
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);
418
419         while (1) {
420                 const char *val = NULL;
421                 int len;
422
423                 len = field_size(home, focus, m, f, &val);
424                 if (o > len) {
425                         if (inc == 0)
426                                 return -1;
427                         if (inc < 0)
428                                 o = len;
429                 }
430
431                 if (inc < 0) {
432                         if (o > 0) {
433                                 o -= 1;
434                                 break;
435                         }
436                         if (f == 0)
437                                 return -1;
438                         /* Try previous field */
439                         f -= 1;
440                         o = 65535;
441                         continue;
442                 }
443                 if (inc > 0) {
444                         if (o < len) {
445                                 o += 1;
446                                 inc = 0;
447                         }
448                         if (o < len)
449                                 break;
450                         if (f >= rd->nfields)
451                                 return -1;
452                         /* Try next field */
453                         f += 1;
454                         o = 0;
455                         continue;
456                 }
457                 /* inc == 0 */
458                 if (o >= len) {
459                         if (f >= rd->nfields)
460                                 return -1;
461                         /* Try next field */
462                         f += 1;
463                         o = 0;
464                         continue;
465                 }
466                 break;
467         }
468         return MAKE_INDEX(f, o);
469 }
470
471 static void update_offset(struct mark *m safe, struct rf_data *rd safe,
472                           unsigned int o)
473 {
474         struct mark *m2 = m;
475         struct mark *target = m;
476         int f;
477
478         /* If o is the first visible field, it needs to be 0 */
479         if (o) {
480                 for (f = 0; f < rd->nfields; f++)
481                         if (rd->fields[f].var ||
482                             rd->fields[f].val_len)
483                                 break;
484                 if (o <= MAKE_INDEX(f, 0))
485                         o = 0;
486         }
487         if (m->ref.i == o)
488                 return;
489
490         if (o > m->ref.i) {
491                 while (m2 && m2->ref.p == m->ref.p && m2->ref.i <= o) {
492                         target = m2;
493                         m2 = mark_next(m2);
494                 }
495         } else {
496                 while (m2 && m2->ref.p == m->ref.p && m2->ref.i >= o) {
497                         target = m2;
498                         m2 = mark_prev(m2);
499                 }
500         }
501         m->ref.i = o;
502         mark_to_mark_noref(m, target);
503 }
504
505 static void prev_line(struct pane *home safe, struct mark *m safe)
506 {
507         struct rf_data *rd = home->data;
508
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);
513                 return;
514         }
515         update_offset(m, rd, MAKE_INDEX(rd->nfields, 0));
516         mark_step(m, 0);
517 }
518
519 static void next_line(struct pane *home safe, struct mark *m safe)
520 {
521         struct rf_data *rd = home->data;
522
523         doc_next(home->parent, m);
524         update_offset(m, rd, MAKE_INDEX(0, 0));
525         mark_step(m, 1);
526 }
527
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)
531 {
532         struct rf_data *rd = home->data;
533         struct rf_field *rf;
534         int move = r == &m->ref;
535         int f, o;
536         int margin;
537         int fsize;
538         int len = 0;
539         const char *val = NULL;
540         int index;
541
542         set_format(focus, rd);
543
544         if (!forward) {
545                 index = normalize(home, focus, m, -1);
546                 if (index < 0) {
547                         if (doc_prior(home->parent, m) == WEOF)
548                                 return CHAR_RET(WEOF);
549                         if (move)
550                                 prev_line(home, m);
551                         return CHAR_RET('\n');
552                 }
553         } else {
554                 if (m->ref.p == NULL)
555                         return CHAR_RET(WEOF);
556                 index = normalize(home, focus, m, 0);
557                 if (index < 0)
558                         /* Should be impossible */
559                         return CHAR_RET(WEOF);
560         }
561         f = FIELD_NUM(index);
562         o = FIELD_OFFSET(index);
563
564         if (f >= rd->nfields) {
565                 if (move)
566                         next_line(home, m);
567                 return CHAR_RET('\n');
568         }
569         rf = &rd->fields[f];
570         fsize = field_size(home, focus, m, f, &val);
571         if (move && forward) {
572                 index = normalize(home, focus, m, 1);
573                 if (index < 0) {
574                         next_line(home, m);
575                         return CHAR_RET('\n');
576                 }
577                 update_offset(m, rd, index);
578         } else if (move && !forward) {
579                 update_offset(m, rd, index);
580         }
581
582         if (!rf->var) {
583                 val = rf->val;
584                 while (o > 0 && get_utf8(&val, NULL) < WERR) {
585                         o -= 1;
586                         if (val[-1] == '%' || val[-1] == '<')
587                                 val += 1;
588                 }
589                 return CHAR_RET(get_utf8(&val, NULL));
590         }
591         if (!val)
592                 return ' ';
593
594         len = utf8_strlen(val);
595         switch (rf->align) {
596         case 'l':
597         default:
598                 if (o < len)
599                         break;
600                 else
601                         return ' ';
602         case 'c':
603                 margin = (fsize - len) / 2;
604                 if (margin < 0)
605                         margin = 0;
606                 if (o < margin)
607                         return ' ';
608                 if (o >= margin + len)
609                         return ' ';
610                 o -= margin;
611                 break;
612         case 'r':
613                 margin = fsize - len;
614                 if (margin < 0)
615                         margin = 0;
616                 if (o < margin)
617                         return ' ';
618                 o -= margin;
619                 break;
620         }
621         while (o > 0 && get_utf8(&val, NULL) < WERR)
622                 o -= 1;
623         return CHAR_RET(get_utf8(&val, NULL));
624 }
625
626 DEF_CMD(format_char)
627 {
628         return do_char_byte(ci);
629 }
630
631 DEF_CMD(format_content2)
632 {
633         /* doc:content delivers one char at a time to a callback.
634          * This is used e.g. for 'search' and 'copy'.
635          *
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.
639          *
640          */
641         struct pane *home = ci->home;
642         struct pane *focus = ci->focus;
643         struct rf_data *rd = home->data;
644         struct rf_field *rf;
645         struct mark *m = ci->mark;
646         struct mark *end = ci->mark2;
647         wint_t nxt, prev;
648         int len, index, f, o, fsize, margin;
649         const char *val;
650         int i;
651
652         if (!m || !ci->comm2)
653                 return Enoarg;
654         if (ci->num)
655                 /* Cannot handle bytes */
656                 return Einval;
657         set_format(focus, rd);
658         m = mark_dup(m);
659
660         pane_set_time(home);
661         do {
662                 if (pane_too_long(home, 2000))
663                         break;
664                 if (m->ref.p == NULL)
665                         break;
666                 index = normalize(home, focus, m, 0);
667                 if (index < 0)
668                         break;
669
670                 f = FIELD_NUM(index);
671                 o = FIELD_OFFSET(index);
672
673                 if (f >= rd->nfields) {
674                         next_line(home, m);
675                         nxt = '\n';
676                         continue;
677                 }
678                 rf = &rd->fields[f];
679                 val = NULL;
680                 fsize = field_size(home, focus, m, f, &val);
681                 mark_step(m, 1);
682                 index = normalize(home, focus, m, 1);
683                 if (index < 0) {
684                         next_line(home, m);
685                         nxt = '\n';
686                         continue;
687                 }
688                 update_offset(m, rd, index);
689
690                 if (!rf->var) {
691                         const char *vend = rf->val + rf->val_len;
692                         prev = WEOF;
693                         val = rf->val;
694                         i = 0;
695                         while ((nxt = get_utf8(&val, vend)) < WERR) {
696                                 if (nxt == '%' || nxt == '<')
697                                         val += 1;
698                                 if (o <= i &&
699                                     (!end || mark_ordered_or_same(m, end))) {
700                                         if (prev != WEOF) {
701                                                 if (comm_call(ci->comm2,
702                                                               "consume", focus,
703                                                               prev, m) <= 0)
704                                                         break;
705                                                 mark_step(m, 1);
706                                                 update_offset(m, rd, MAKE_INDEX(f, i+1));
707                                         }
708                                         prev = nxt;
709                                 }
710                                 i += 1;
711                         }
712                         nxt = prev;
713                         continue;
714                 }
715                 if (!val) {
716                         nxt = ' ';
717                         continue;
718                 }
719                 len = utf8_strlen(val);
720                 switch (rf->align) {
721                 case 'l':
722                 default:
723                         margin = 0;
724                         break;
725                 case 'c':
726                         margin = (fsize - len) / 2;
727                         if (margin < 0)
728                                 margin = 0;
729                         break;
730                 case 'r':
731                         margin = fsize - len;
732                         if (margin < 0)
733                                 margin = 0;
734                         break;
735                 }
736                 prev = nxt = WEOF;
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' &&
742                               i >= len))
743                                 nxt = ' ';
744                         else
745                                 nxt = get_utf8(&val, NULL);
746                         if (i >= o) {
747                                 if (prev != WEOF) {
748                                         if (comm_call(ci->comm2,
749                                                       "consume", focus,
750                                                       prev, m) <= 0)
751                                                 break;
752                                         mark_step(m, 1);
753                                         update_offset(m, rd, MAKE_INDEX(f, i+1));
754                                 }
755                                 prev = nxt;
756                         }
757                 }
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);
761
762         mark_free(m);
763         return 1;
764 }
765
766 DEF_CMD(format_attr)
767 {
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.
771          *
772          * Also "format:plain" which formats the line directly
773          * without the cost of all the lib-markup machinery.
774          */
775         struct rf_data *rd = ci->home->data;
776         struct mark *m = ci->mark;
777         int previ;
778         int f0, f;
779         int idx;
780         bool need_attr = False;
781
782         if (!m || !ci->str)
783                 return Enoarg;
784         if (!m->ref.p)
785                 return Efallthrough;
786         if (strcmp(ci->str, "format:plain") == 0) {
787                 char *v = do_format(ci->focus, m, NULL, -1, 0);
788
789                 comm_call(ci->comm2, "", ci->focus, 0, m, v);
790                 free(v);
791         }
792
793         if (ci->num2 == 0 && strcmp(ci->str, "render:format") != 0)
794                 return Efallthrough;
795         if (ci->num2 && strncmp(ci->str, "render:format", strlen(ci->str)) != 0)
796                 return Efallthrough;
797
798         idx = m->ref.i;
799         /* idx of 0 is special and may not be normalized */
800         if (idx == 0)
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 */
804                 return 1;
805
806         /* There may be several previous fields that are empty.
807          * We need consider the possibility that any of those
808          * change the attributes.
809          */
810         previ = normalize(ci->home, ci->focus, m, -1);
811         if (previ < 0)
812                 f0 = 0;
813         else
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)
819                                 need_attr = True;
820                 }
821         }
822         if (need_attr) {
823                 if (strcmp(ci->str, "render:format") == 0)
824                         comm_call(ci->comm2, "", ci->focus, 0, m, "yes");
825                 else
826                         comm_call(ci->comm2, "", ci->focus, 0, m, "yes",
827                                   0, NULL, "render:format");
828         }
829         return 1;
830 }
831
832 DEF_CMD(format_map)
833 {
834         struct rf_data *rd = ci->home->data;
835         struct mark *m = ci->mark;
836         int idx, previ;
837         int f0, f;
838
839         if (!m || !ci->str)
840                 return Enoarg;
841         if (strcmp(ci->str, "render:format") != 0)
842                 return Efallthrough;
843         if (m->ref.p == NULL)
844                 return Efallthrough;
845         idx = m->ref.i;
846         if (idx == 0)
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 */
850                 return 1;
851         f = FIELD_NUM(idx);
852
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.
856          */
857         previ = normalize(ci->home, ci->focus, m, -1);
858         if (previ < 0)
859                 f0 = 0;
860         else
861                 f0 = FIELD_NUM(previ)+1;
862         for(f = f0; f <= FIELD_NUM(idx); f++) {
863                 if (f >= rd->nfields)
864                         continue;
865                 /* Each depth gets a priority level from 0 up.
866                  * When starting, set length to v.large.  When ending, set
867                  * length to -1.
868                  */
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);
874                 }
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);
882                 }
883         }
884         return 0;
885 }
886
887 DEF_CMD(render_line_prev2)
888 {
889         struct rf_data *rd = ci->home->data;
890         struct mark *m = ci->mark;
891         struct mark *m2, *mn;
892
893         if (!m)
894                 return Enoarg;
895         if (RPT_NUM(ci) == 0)
896                 ;
897         else if (doc_prev(ci->home->parent, m) == WEOF)
898                 /* Hit start-of-file */
899                 return Efail;
900         m2 = m;
901         while ((mn = mark_prev(m2)) != NULL &&
902                mn->ref.p == m2->ref.p &&
903                mn->ref.i > 0)
904                 m2 = mn;
905         mark_to_mark(m, m2);
906         update_offset(m, rd, 0);
907
908         return 1;
909 }
910
911 static struct pane *do_render_format_attach(struct pane *parent safe);
912 DEF_CMD(format_clone)
913 {
914         struct pane *p;
915
916         p = do_render_format_attach(ci->focus);
917         pane_clone_children(ci->home, p);
918         return 1;
919 }
920
921 DEF_CMD(format_noshare_ref)
922 {
923         return Efalse;
924 }
925
926 static struct map *rf_map, *rf2_map;
927
928 static void render_format_register_map(void)
929 {
930         rf_map = key_alloc();
931
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);
936
937         rf2_map = key_alloc();
938
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);
947 }
948
949 DEF_LOOKUP_CMD(render_format_handle, rf_map);
950 DEF_LOOKUP_CMD(render_format2_handle, rf2_map);
951
952 static struct pane *do_render_format_attach(struct pane *parent safe)
953 {
954         struct pane *p;
955
956         if (call("doc:shares-ref", parent) != 1) {
957                 if (!rf_map)
958                         render_format_register_map();
959
960                 p = pane_register(parent, 0, &render_format_handle.c, NULL);
961         } else {
962                 if (!rf2_map)
963                         render_format_register_map();
964
965                 p = pane_register(parent, 0, &render_format2_handle.c);
966                 if (!p)
967                         return p;
968
969                 if (!pane_attr_get(parent, "format:no-linecount")) {
970                         struct pane *p2 = call_ret(pane, "attach-line-count", p);
971                         if (p2)
972                                 p = p2;
973                 }
974         }
975         if (!p)
976                 return NULL;
977         attr_set_str(&p->attrs, "render-wrap", "no");
978         return p;
979 }
980
981 DEF_CMD(render_format_attach)
982 {
983         struct pane *p;
984
985         p = do_render_format_attach(ci->focus);
986         if (!p)
987                 return Efail;
988         if (p->handle == &render_format_handle.c)
989                 p = call_ret(pane, "attach-render-lines", p);
990         else
991                 p = call_ret(pane, "attach-render-text", p);
992         if (!p)
993                 return Efail;
994         return comm_call(ci->comm2, "callback:attach", p);
995 }
996
997 void edlib_init(struct pane *ed safe)
998 {
999         call_comm("global-set-command", ed, &render_format_attach, 0, NULL, "attach-render-format");
1000 }