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