]> git.neil.brown.name Git - edlib.git/blob - render-format.c
Introduce PANE_DATA_PTR_TYPE for mode-emacs.
[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;
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_CLOSED(format_close)
239 {
240         struct rf_data *rf = ci->home->data;
241
242         free(rf->attr_cache); rf->attr_cache = NULL;
243         free(rf->fields); rf->fields = NULL;
244         free(rf->format); rf->format = "";
245         rf->nfields = 0;
246         return 1;
247 }
248
249 static struct rf_field *new_field(struct rf_data *rd safe)
250 {
251         struct rf_field *rf;
252
253         if (rd->nfields >= rd->alloc_fields) {
254                 if (rd->alloc_fields >= 32768)
255                         return NULL;
256                 if (rd->alloc_fields < 8)
257                         rd->alloc_fields = 8;
258                 else
259                         rd->alloc_fields *= 2;
260                 rd->fields = realloc(rd->fields,
261                                      sizeof(*rd->fields) * rd->alloc_fields);
262         }
263         rf = &rd->fields[rd->nfields++];
264         memset(rf, 0, sizeof(*rf));
265         rf->attr_start = rd->nfields; /* i.e. no attr ends here */
266         if (rd->nfields > 1)
267                 rf->attr_depth = rd->fields[rd->nfields-2].attr_depth;
268         return rf;
269 }
270
271 static char *rf_add_field(struct rf_data *rd safe, char *str safe)
272 {
273         struct rf_field *rf = new_field(rd);
274
275         if (!rf)
276                 return NULL;
277
278         if (str[0] == '<' && str[1] == '/' && str[2] == '>') {
279                 int start;
280                 str += 3;
281                 for (start = rd->nfields-2 ; start >= 0; start -= 1)
282                         if (rd->fields[start].attr &&
283                             rd->fields[start].attr_end == 0)
284                                 break;
285                 if (start >= 0) {
286                         rd->fields[start].attr_end = rd->nfields-1;
287                         rf->attr_start = start;
288                         rf->attr_depth -= 1;
289                 }
290         }
291         if (str[0] == '<' && str[1] != '<' && (str[1] != '/' || str[2] != '>')) {
292                 rf->attr = str+1;
293                 rf->attr_depth += 1;
294                 while (str[0] && str[0] != '>')
295                         str++;
296                 if (str[0])
297                         *str++ = 0;
298         }
299         if (str[0] == '<' && str[1] != '<')
300                 /* More attr start/stop, must go in next field */
301                 return str;
302
303         if (str[0] != '%' || str[1] == '%') {
304                 /* Must be literal */
305                 rf->val = str;
306                 if (str[0] == '<' || str[0] == '%') {
307                         /* must be '<<' or '%%', only include second
308                          * in ->val */
309                         rf->val += 1;
310                         str += 2;
311                         rf->val_len = 1;
312                 }
313                 while (str[0] && str[0] != '<' && str[0] != '%') {
314                         get_utf8((const char **)&str, NULL);
315                         rf->val_len += 1;
316                 }
317                 return str;
318         }
319         /* This is a '%' field */
320         str += 1;
321         rf->val = str;
322         rf->align = 'l';
323         rf->var = True;
324         while (*str == '-' || *str == '_' || isalnum(*str))
325                 str += 1;
326         rf->val_len = str - rf->val;
327         if (*str != ':')
328                 return str;
329         str += 1;
330         if (*str == '-') {
331                 str += 1;
332                 rf->align = 'r';
333         }
334         while (isdigit(*str)) {
335                 rf->width *= 10;
336                 rf->width += *str - '0';
337                 str += 1;
338         }
339         return str;
340 }
341
342 static void set_format(struct pane *focus safe, struct rf_data *rd safe)
343 {
344         char *str;
345         int f;
346
347         if (rd->format)
348                 return;
349         str = pane_attr_get(focus, "line-format");
350         rd->format = strdup(str ?: "%name");
351         str = rd->format;
352         while (str && *str)
353                 str = rf_add_field(rd, str);
354
355         for (f = rd->nfields - 1; f >= 0; f--) {
356                 struct rf_field *rf = &rd->fields[f];
357
358                 if (rf->attr && rf->attr_end == 0)
359                         rf_add_field(rd, "</>");
360         }
361 }
362
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)
366 {
367         struct rf_data *rd = home->data;
368         struct rf_field *rf;
369         const char *val;
370         int l;
371
372         if (field < 0 || field > rd->nfields)
373                 return 0;
374         if (field == rd->nfields) {
375                 /* Just a newline at the end */
376                 *valp = "\n";
377                 return 1;
378         }
379         rf = &rd->fields[field];
380         if (!rf->var)
381                 return rf->val_len;
382         val = *valp;
383         if (val)
384                 ;
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);
389         } else {
390                 char b[80];
391                 strncpy(b, rf->val, 80);
392                 b[79] = 0;
393                 if (rf->val_len < 80)
394                         b[rf->val_len] = 0;
395                 val = pane_mark_attr(focus, m, b);
396                 if (!val)
397                         val = "-";
398                 *valp = val;
399                 if (rd->attr_cache)
400                         free(rd->attr_cache);
401                 rd->attr_cache = strdup(val);
402                 rd->cache_field = field;
403                 rd->cache_pos = m->ref.p;
404         }
405         l = utf8_strlen(val);
406         if (l < rf->width)
407                 return rf->width;
408         else
409                 return l;
410 }
411
412 static int normalize(struct pane *home safe, struct pane *focus safe,
413                      struct mark *m safe, int inc)
414 {
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);
419
420         while (1) {
421                 const char *val = NULL;
422                 int len;
423
424                 len = field_size(home, focus, m, f, &val);
425                 if (o > len) {
426                         if (inc == 0)
427                                 return -1;
428                         if (inc < 0)
429                                 o = len;
430                 }
431
432                 if (inc < 0) {
433                         if (o > 0) {
434                                 o -= 1;
435                                 break;
436                         }
437                         if (f == 0)
438                                 return -1;
439                         /* Try previous field */
440                         f -= 1;
441                         o = 65535;
442                         continue;
443                 }
444                 if (inc > 0) {
445                         if (o < len) {
446                                 o += 1;
447                                 inc = 0;
448                         }
449                         if (o < len)
450                                 break;
451                         if (f >= rd->nfields)
452                                 return -1;
453                         /* Try next field */
454                         f += 1;
455                         o = 0;
456                         continue;
457                 }
458                 /* inc == 0 */
459                 if (o >= len) {
460                         if (f >= rd->nfields)
461                                 return -1;
462                         /* Try next field */
463                         f += 1;
464                         o = 0;
465                         continue;
466                 }
467                 break;
468         }
469         return MAKE_INDEX(f, o);
470 }
471
472 static void update_offset(struct mark *m safe, struct rf_data *rd safe,
473                           unsigned int o)
474 {
475         struct mark *m2 = m;
476         struct mark *target = m;
477         int f;
478
479         if (!rd->fields)
480                 return;
481         /* If o is the first visible field, it needs to be 0 */
482         if (o) {
483                 for (f = 0; f < rd->nfields; f++)
484                         if (rd->fields[f].var ||
485                             rd->fields[f].val_len)
486                                 break;
487                 if (o <= MAKE_INDEX(f, 0))
488                         o = 0;
489         }
490         if (m->ref.i == o)
491                 return;
492
493         if (o > m->ref.i) {
494                 while (m2 && m2->ref.p == m->ref.p && m2->ref.i <= o) {
495                         target = m2;
496                         m2 = mark_next(m2);
497                 }
498         } else {
499                 while (m2 && m2->ref.p == m->ref.p && m2->ref.i >= o) {
500                         target = m2;
501                         m2 = mark_prev(m2);
502                 }
503         }
504         m->ref.i = o;
505         mark_to_mark_noref(m, target);
506 }
507
508 static void prev_line(struct pane *home safe, struct mark *m safe)
509 {
510         struct rf_data *rd = home->data;
511
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);
516                 return;
517         }
518         update_offset(m, rd, MAKE_INDEX(rd->nfields, 0));
519         mark_step(m, 0);
520 }
521
522 static void next_line(struct pane *home safe, struct mark *m safe)
523 {
524         struct rf_data *rd = home->data;
525
526         doc_next(home->parent, m);
527         update_offset(m, rd, MAKE_INDEX(0, 0));
528         mark_step(m, 1);
529 }
530
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)
534 {
535         struct rf_data *rd = home->data;
536         struct rf_field *rf;
537         int move = r == &m->ref;
538         int f, o;
539         int margin;
540         int fsize;
541         int len = 0;
542         const char *val = NULL;
543         int index;
544
545         set_format(focus, rd);
546
547         if (!forward) {
548                 index = normalize(home, focus, m, -1);
549                 if (index < 0) {
550                         if (doc_prior(home->parent, m) == WEOF)
551                                 return CHAR_RET(WEOF);
552                         if (move)
553                                 prev_line(home, m);
554                         return CHAR_RET('\n');
555                 }
556         } else {
557                 if (m->ref.p == NULL)
558                         return CHAR_RET(WEOF);
559                 index = normalize(home, focus, m, 0);
560                 if (index < 0)
561                         /* Should be impossible */
562                         return CHAR_RET(WEOF);
563         }
564         f = FIELD_NUM(index);
565         o = FIELD_OFFSET(index);
566
567         if (f >= rd->nfields) {
568                 if (move)
569                         next_line(home, m);
570                 return CHAR_RET('\n');
571         }
572         rf = &rd->fields[f];
573         fsize = field_size(home, focus, m, f, &val);
574         if (move && forward) {
575                 index = normalize(home, focus, m, 1);
576                 if (index < 0) {
577                         next_line(home, m);
578                         return CHAR_RET('\n');
579                 }
580                 update_offset(m, rd, index);
581         } else if (move && !forward) {
582                 update_offset(m, rd, index);
583         }
584
585         if (!rf->var) {
586                 val = rf->val;
587                 while (o > 0 && get_utf8(&val, NULL) < WERR) {
588                         o -= 1;
589                         if (val[-1] == '%' || val[-1] == '<')
590                                 val += 1;
591                 }
592                 return CHAR_RET(get_utf8(&val, NULL));
593         }
594         if (!val)
595                 return ' ';
596
597         len = utf8_strlen(val);
598         switch (rf->align) {
599         case 'l':
600         default:
601                 if (o < len)
602                         break;
603                 else
604                         return ' ';
605         case 'c':
606                 margin = (fsize - len) / 2;
607                 if (margin < 0)
608                         margin = 0;
609                 if (o < margin)
610                         return ' ';
611                 if (o >= margin + len)
612                         return ' ';
613                 o -= margin;
614                 break;
615         case 'r':
616                 margin = fsize - len;
617                 if (margin < 0)
618                         margin = 0;
619                 if (o < margin)
620                         return ' ';
621                 o -= margin;
622                 break;
623         }
624         while (o > 0 && get_utf8(&val, NULL) < WERR)
625                 o -= 1;
626         return CHAR_RET(get_utf8(&val, NULL));
627 }
628
629 DEF_CMD(format_char)
630 {
631         return do_char_byte(ci);
632 }
633
634 DEF_CMD(format_content2)
635 {
636         /* doc:content delivers one char at a time to a callback.
637          * This is used e.g. for 'search' and 'copy'.
638          *
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.
642          *
643          */
644         struct pane *home = ci->home;
645         struct pane *focus = ci->focus;
646         struct rf_data *rd = home->data;
647         struct rf_field *rf;
648         struct mark *m = ci->mark;
649         struct mark *end = ci->mark2;
650         wint_t nxt, prev;
651         int len, index, f, o, fsize, margin;
652         const char *val;
653         int i;
654
655         if (!m || !ci->comm2)
656                 return Enoarg;
657         if (ci->num)
658                 /* Cannot handle bytes */
659                 return Einval;
660         set_format(focus, rd);
661         m = mark_dup(m);
662
663         pane_set_time(home);
664         do {
665                 if (pane_too_long(home, 2000))
666                         break;
667                 if (m->ref.p == NULL)
668                         break;
669                 index = normalize(home, focus, m, 0);
670                 if (index < 0)
671                         break;
672
673                 f = FIELD_NUM(index);
674                 o = FIELD_OFFSET(index);
675
676                 if (f >= rd->nfields) {
677                         next_line(home, m);
678                         nxt = '\n';
679                         continue;
680                 }
681                 rf = &rd->fields[f];
682                 val = NULL;
683                 fsize = field_size(home, focus, m, f, &val);
684                 mark_step(m, 1);
685                 index = normalize(home, focus, m, 1);
686                 if (index < 0) {
687                         next_line(home, m);
688                         nxt = '\n';
689                         continue;
690                 }
691                 update_offset(m, rd, index);
692
693                 if (!rf->var) {
694                         const char *vend = rf->val + rf->val_len;
695                         prev = WEOF;
696                         val = rf->val;
697                         i = 0;
698                         while ((nxt = get_utf8(&val, vend)) < WERR) {
699                                 if (nxt == '%' || nxt == '<')
700                                         val += 1;
701                                 if (o <= i &&
702                                     (!end || mark_ordered_or_same(m, end))) {
703                                         if (prev != WEOF) {
704                                                 if (comm_call(ci->comm2,
705                                                               "consume", focus,
706                                                               prev, m) <= 0)
707                                                         break;
708                                                 mark_step(m, 1);
709                                                 update_offset(m, rd, MAKE_INDEX(f, i+1));
710                                         }
711                                         prev = nxt;
712                                 }
713                                 i += 1;
714                         }
715                         nxt = prev;
716                         continue;
717                 }
718                 if (!val) {
719                         nxt = ' ';
720                         continue;
721                 }
722                 len = utf8_strlen(val);
723                 switch (rf->align) {
724                 case 'l':
725                 default:
726                         margin = 0;
727                         break;
728                 case 'c':
729                         margin = (fsize - len) / 2;
730                         if (margin < 0)
731                                 margin = 0;
732                         break;
733                 case 'r':
734                         margin = fsize - len;
735                         if (margin < 0)
736                                 margin = 0;
737                         break;
738                 }
739                 prev = nxt = WEOF;
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' &&
745                               i >= len))
746                                 nxt = ' ';
747                         else
748                                 nxt = get_utf8(&val, NULL);
749                         if (i >= o) {
750                                 if (prev != WEOF) {
751                                         if (comm_call(ci->comm2,
752                                                       "consume", focus,
753                                                       prev, m) <= 0)
754                                                 break;
755                                         mark_step(m, 1);
756                                         update_offset(m, rd, MAKE_INDEX(f, i+1));
757                                 }
758                                 prev = nxt;
759                         }
760                 }
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);
764
765         mark_free(m);
766         return 1;
767 }
768
769 DEF_CMD(format_attr)
770 {
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.
774          *
775          * Also "format:plain" which formats the line directly
776          * without the cost of all the lib-markup machinery.
777          */
778         struct rf_data *rd = ci->home->data;
779         struct mark *m = ci->mark;
780         int previ;
781         int f0, f;
782         int idx;
783         bool need_attr = False;
784
785         if (!m || !ci->str)
786                 return Enoarg;
787         if (!m->ref.p)
788                 return Efallthrough;
789         if (!rd->fields)
790                 return Efail;
791         if (strcmp(ci->str, "format:plain") == 0) {
792                 char *v = do_format(ci->focus, m, NULL, -1, 0);
793
794                 comm_call(ci->comm2, "", ci->focus, 0, m, v);
795                 free(v);
796         }
797
798         if (ci->num2 == 0 && strcmp(ci->str, "render:format") != 0)
799                 return Efallthrough;
800         if (ci->num2 && strncmp(ci->str, "render:format", strlen(ci->str)) != 0)
801                 return Efallthrough;
802
803         idx = m->ref.i;
804         /* idx of 0 is special and may not be normalized */
805         if (idx == 0)
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 */
809                 return 1;
810
811         /* There may be several previous fields that are empty.
812          * We need consider the possibility that any of those
813          * change the attributes.
814          */
815         previ = normalize(ci->home, ci->focus, m, -1);
816         if (previ < 0)
817                 f0 = 0;
818         else
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)
824                                 need_attr = True;
825                 }
826         }
827         if (need_attr) {
828                 if (strcmp(ci->str, "render:format") == 0)
829                         comm_call(ci->comm2, "", ci->focus, 0, m, "yes");
830                 else
831                         comm_call(ci->comm2, "", ci->focus, 0, m, "yes",
832                                   0, NULL, "render:format");
833         }
834         return 1;
835 }
836
837 DEF_CMD(format_map)
838 {
839         struct rf_data *rd = ci->home->data;
840         struct mark *m = ci->mark;
841         int idx, previ;
842         int f0, f;
843
844         if (!m || !ci->str)
845                 return Enoarg;
846         if (strcmp(ci->str, "render:format") != 0)
847                 return Efallthrough;
848         if (m->ref.p == NULL)
849                 return Efallthrough;
850         idx = m->ref.i;
851         if (idx == 0)
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 */
855                 return 1;
856         f = FIELD_NUM(idx);
857
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.
861          */
862         previ = normalize(ci->home, ci->focus, m, -1);
863         if (previ < 0)
864                 f0 = 0;
865         else
866                 f0 = FIELD_NUM(previ)+1;
867         for(f = f0; f <= FIELD_NUM(idx); f++) {
868                 if (f >= rd->nfields || !rd->fields)
869                         continue;
870                 /* Each depth gets a priority level from 0 up.
871                  * When starting, set length to v.large.  When ending, set
872                  * length to -1.
873                  */
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);
879                 }
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);
887                 }
888         }
889         return 0;
890 }
891
892 DEF_CMD(render_line_prev2)
893 {
894         struct rf_data *rd = ci->home->data;
895         struct mark *m = ci->mark;
896         struct mark *m2, *mn;
897
898         if (!m)
899                 return Enoarg;
900         if (RPT_NUM(ci) == 0)
901                 ;
902         else if (doc_prev(ci->home->parent, m) == WEOF)
903                 /* Hit start-of-file */
904                 return Efail;
905         m2 = m;
906         while ((mn = mark_prev(m2)) != NULL &&
907                mn->ref.p == m2->ref.p &&
908                mn->ref.i > 0)
909                 m2 = mn;
910         mark_to_mark(m, m2);
911         update_offset(m, rd, 0);
912
913         return 1;
914 }
915
916 static struct pane *do_render_format_attach(struct pane *parent safe);
917 DEF_CMD(format_clone)
918 {
919         struct pane *p;
920
921         p = do_render_format_attach(ci->focus);
922         pane_clone_children(ci->home, p);
923         return 1;
924 }
925
926 DEF_CMD(format_noshare_ref)
927 {
928         return Efalse;
929 }
930
931 static struct map *rf_map, *rf2_map;
932
933 static void render_format_register_map(void)
934 {
935         rf_map = key_alloc();
936
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);
941
942         rf2_map = key_alloc();
943
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);
952 }
953
954 DEF_LOOKUP_CMD(render_format_handle, rf_map);
955 DEF_LOOKUP_CMD(render_format2_handle, rf2_map);
956
957 static struct pane *do_render_format_attach(struct pane *parent safe)
958 {
959         struct pane *p;
960
961         if (call("doc:shares-ref", parent) != 1) {
962                 if (!rf_map)
963                         render_format_register_map();
964
965                 p = pane_register(parent, 0, &render_format_handle.c, NULL);
966         } else {
967                 if (!rf2_map)
968                         render_format_register_map();
969
970                 p = pane_register(parent, 0, &render_format2_handle.c);
971                 if (!p)
972                         return p;
973
974                 if (!pane_attr_get(parent, "format:no-linecount")) {
975                         struct pane *p2 = call_ret(pane, "attach-line-count", p);
976                         if (p2)
977                                 p = p2;
978                 }
979         }
980         if (!p)
981                 return NULL;
982         attr_set_str(&p->attrs, "render-wrap", "no");
983         return p;
984 }
985
986 DEF_CMD(render_format_attach)
987 {
988         struct pane *p;
989
990         p = do_render_format_attach(ci->focus);
991         if (!p)
992                 return Efail;
993         if (p->handle == &render_format_handle.c)
994                 p = call_ret(pane, "attach-render-lines", p);
995         else
996                 p = call_ret(pane, "attach-render-text", p);
997         if (!p)
998                 return Efail;
999         return comm_call(ci->comm2, "callback:attach", p);
1000 }
1001
1002 void edlib_init(struct pane *ed safe)
1003 {
1004         call_comm("global-set-command", ed, &render_format_attach, 0, NULL, "attach-render-format");
1005 }