]> git.neil.brown.name Git - edlib.git/blobdiff - lib-markup.c
TODO: clean out done items.
[edlib.git] / lib-markup.c
index a3fcfc26d151d9edc1befb6a58b92b58bc3313f0..0c8f51b3d41aa78ac8a0e310107ff6ad3b40da30 100644 (file)
@@ -1,15 +1,12 @@
 /*
- * Copyright Neil Brown ©2016-2019 <neil@brown.name>
+ * Copyright Neil Brown ©2016-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * This module provides render-line and render-line-prev, making use of
- * the chars returned by doc:step.
+ * the chars returned by doc:char.
  * A line is normally text ending with a newline.  However if no newline
  * is found in a long distance, we drop a mark and use that as the start
  * of a line.
- * A vertial tab '\v' acts like a newline but forces it to be a blank line.
- * A "\v" immediately after "\m" or "\v" is exactly like a newline, while
- * "\v" after anything else terminates the line without consuming the newline.
  */
 
 #include <unistd.h>
 #include <string.h>
 
 #include <stdio.h>
-
+#define PANE_DATA_TYPE struct mu_info
 #include "core.h"
 #include "misc.h"
 
-struct rl_info {
+struct mu_info {
        int     view;
 };
+#include "core-pane.h"
+
+static struct map *mu_map safe;
 
-static struct map *rl_map safe;
+#define LARGE_LINE 5000
 
-#define LARGE_LINE 1000
+static int is_render_eol(wint_t ch, struct pane *p safe, struct mark *m safe)
+{
+       char *attr;
+       if (!is_eol(ch))
+               return 0;
+       attr = pane_mark_attr(p, m, "markup:not_eol");
+       if (attr && *attr)
+               return 0;
+       return 1;
+}
 
 DEF_CMD(render_prev)
 {
@@ -46,8 +55,9 @@ DEF_CMD(render_prev)
         */
        struct mark *m = ci->mark;
        struct pane *f = ci->focus;
-       struct rl_info *rl = ci->home->data;
+       struct mu_info *mu = ci->home->data;
        struct mark *boundary = NULL;
+       struct mark *doc_boundary = NULL;
        int count = 0;
        int rpt = RPT_NUM(ci);
        wint_t ch;
@@ -55,30 +65,43 @@ DEF_CMD(render_prev)
        if (!m)
                return Enoarg;
 
-       while ((ch = mark_prev_pane(f, m)) != WEOF &&
-              (!is_eol(ch) || rpt > 0) &&
+       if (!rpt) {
+               boundary = vmark_at_or_before(f, m, mu->view, ci->home);
+               doc_boundary = call_ret(mark, "doc:get-boundary", f, -1, m);
+       }
+       while ((ch = doc_prev(f, m)) != WEOF &&
+              (!is_render_eol(ch, f, m) || rpt > 0) &&
               count < LARGE_LINE &&
-              (!boundary || boundary->seq< m->seq)) {
+              (!boundary || mark_ordered_not_same(boundary, m)) &&
+              (!doc_boundary || mark_ordered_not_same(doc_boundary, m))) {
+               if (rpt) {
+                       boundary = vmark_at_or_before(f, m, mu->view, ci->home);
+                       doc_boundary = call_ret(mark, "doc:get-boundary", f, -1, m);
+               }
                rpt = 0;
-               if (!count)
-                       boundary = vmark_at_or_before(f, m, rl->view, ci->home);
                count += 1;
        }
-       if (ch != WEOF && !is_eol(ch)) {
-               /* need to ensure there is a stable boundary here */
-               if (!boundary || boundary->seq >= m->seq) {
-                       boundary = vmark_new(f, rl->view, ci->home);
+       if (ch != WEOF && !is_render_eol(ch, f, m) &&
+           (!doc_boundary || !mark_same(doc_boundary, m))) {
+               /* Just crossed the boundary, or the max count.
+                * Need to step back, and ensure there is a stable boundary
+                * here.
+                */
+               mark_free(doc_boundary);
+               doc_next(f, m);
+               if (!boundary || !mark_same(boundary, m)) {
+                       boundary = vmark_new(f, mu->view, ci->home);
                        if (boundary)
                                mark_to_mark(boundary, m);
                }
                return 1;
        }
+       mark_free(doc_boundary);
        if (ch == WEOF && rpt)
                return Efail;
-       if (ch == '\n' || (ch == '\v' &&
-                          ((ch = doc_prior_pane(f, m)) == WEOF || !is_eol(ch))))
-               /* Found a '\n', so step forward over it for start-of-line. */
-               mark_next_pane(f, m);
+       /* Found a '\n', so step forward over it for start-of-line. */
+       if (is_render_eol(ch, f, m))
+               doc_next(f, m);
        return 1;
 }
 
@@ -90,8 +113,8 @@ DEF_CMD(render_prev)
  * To change an attribute (normally add or delete) we pop it and any attributes
  * above it in the stack and push them onto tmpst, which is then in
  * reverse priority order.  As we do that, we count them in 'popped'.
- * Changes can be made in the secondard stack.
- * When all change have been made, we add 'popped' "</>" marked to the output,
+ * Changes can be made in the secondary stack.
+ * When all change have been made, we add 'popped' ETX marked to the output,
  * then process everything in 'tmpst', either discarding it if end<=chars, or
  * outputting the attributes and pushing back on 'ast'.
  */
@@ -102,10 +125,11 @@ struct attr_return {
                struct attr_stack       *next;
                char                    *attr safe;
                int                     end;
-               short                   priority;
+               unsigned short          priority;
        } *ast, *tmpst;
        int min_end;
        int chars;
+       struct buf insert;
        short popped;
 };
 
@@ -158,7 +182,7 @@ static void as_repush(struct attr_return *ar safe, struct buf *b safe)
        struct attr_stack *to = ar->ast;
 
        while (ar->popped > 0) {
-               buf_concat(b, "</>");
+               buf_append(b, etx);
                ar->popped -= 1;
        }
 
@@ -168,9 +192,9 @@ static void as_repush(struct attr_return *ar safe, struct buf *b safe)
                        free(from->attr);
                        free(from);
                } else {
-                       buf_append(b, '<');
+                       buf_append(b, soh);
                        buf_concat(b, from->attr);
-                       buf_append(b, '>');
+                       buf_append(b, stx);
                        from->next = to;
                        to = from;
                        if (from->end < ar->min_end)
@@ -196,7 +220,7 @@ static void as_add(struct attr_return *ar safe,
        new = calloc(1, sizeof(*new));
        new->next = *here;
        new->attr = strdup(attr);
-       if (INT_MAX - end <= ar->chars)
+       if (end == 0 || INT_MAX - end <= ar->chars)
                end = INT_MAX - 1 - ar->chars;
        new->end = ar->chars + end;
        new->priority = prio;
@@ -204,7 +228,7 @@ static void as_add(struct attr_return *ar safe,
 }
 
 static void as_clear(struct attr_return *ar safe,
-                    int prio, const char *attr safe)
+                    int prio, const char *attr)
 {
        struct attr_stack *st;
 
@@ -212,29 +236,40 @@ static void as_clear(struct attr_return *ar safe,
                as_pop(ar, 1);
 
        for (st = ar->tmpst; st && st->priority <= prio; st = st->next)
-               if (st->priority == prio && strcmp(st->attr, attr) == 0)
+               if (st->priority == prio &&
+                   (attr == NULL || strcmp(st->attr, attr) == 0))
                        st->end = ar->chars;
 }
 
-DEF_CMD(text_attr_forward)
+DEF_CB(text_attr_forward)
 {
        struct attr_return *ar = container_of(ci->comm, struct attr_return, fwd);
        if (!ci->str || !ci->str2)
-               return 0;
+               return Enoarg;
        return call_comm("map-attr", ci->focus, &ar->rtn, 0, ci->mark, ci->str2,
                         0, NULL, ci->str);
 }
 
-DEF_CMD(text_attr_callback)
+DEF_CB(text_attr_callback)
 {
-       struct attr_return *ar = container_of(ci->comm, struct attr_return, rtn);
-       if (!ci->str)
-               return Enoarg;
-       if (ci->num >= 0)
-               as_add(ar, ci->num, ci->num2, ci->str);
-       else
-               as_clear(ar, ci->num2, ci->str);
-       // FIXME ->str2 should be inserted
+       struct attr_return *ar = container_of(ci->comm, struct attr_return,
+                                             rtn);
+       int prio = ci->num2;
+       if (prio < 0)
+               prio = 0;
+       if (prio > 65535)
+               prio = 65535;
+       if (ci->num >= 0) {
+               if (ci->str)
+                       as_add(ar, ci->num, prio, ci->str);
+       } else
+               as_clear(ar, prio, ci->str);
+       if (ci->str2) {
+               const char *c = ci->str2;
+               wint_t wch;
+               while ((wch = get_utf8(&c, NULL)) != WEOF)
+                       buf_append(&ar->insert, wch);
+       }
        return 1;
 }
 
@@ -245,7 +280,7 @@ static void call_map_mark(struct pane *f safe, struct mark *m safe,
        const char *val;
 
        while ((key = attr_get_next_key(m->attrs, key, -1, &val)) != NULL &&
-              strncmp(key, "render:", 7) == 0)
+              strstarts(key, "render:"))
                call_comm("map-attr", f, &ar->rtn, 0, m, key, 0, NULL, val);
 }
 
@@ -259,33 +294,32 @@ static int want_vis_newline(struct attr_stack *as)
 DEF_CMD(render_line)
 {
        /* Render the line from 'mark' to the first '\n' or until
-        * 'num2' chars.
-        * Convert '<' to '<<' and if a char has the 'highlight' attribute,
-        * include that between '<>'.
+        * 'num' chars.
         */
        struct buf b;
        struct pane *focus = ci->focus;
-       struct rl_info *rl = ci->home->data;
+       struct mu_info *mu = ci->home->data;
        struct mark *m = ci->mark;
        struct mark *pm = ci->mark2; /* The location to render as cursor */
-       struct mark *boundary;
+       struct mark *boundary, *start_boundary = NULL;
+       struct mark *doc_boundary;
        int o = ci->num;
+       int pm_offset = -1;
        wint_t ch;
        int chars = 0;
        int ret;
        struct attr_return ar;
        int add_newline = 0;
        char *oneline;
+       char *noret;
        char *attr;
 
-       if (o == NO_NUMERIC)
-               o = -1;
-
        ar.rtn = text_attr_callback;
        ar.fwd = text_attr_forward;
        ar.ast = ar.tmpst = NULL;
        ar.min_end = -1;
        ar.chars = 0;
+       buf_init(&ar.insert);
        ar.popped = 0;
 
        if (!m)
@@ -294,28 +328,50 @@ DEF_CMD(render_line)
        oneline = pane_attr_get(focus, "render-one-line");
        if (oneline && strcmp(oneline, "yes") != 0)
                oneline = NULL;
+       noret = pane_attr_get(focus, "render-hide-CR");
+       if (noret && strcmp(noret, "yes") != 0)
+               noret = NULL;
 
-       ch = doc_following_pane(focus, m);
-       if (is_eol(ch) &&
-           (attr = pane_mark_attr(focus, m, "markup:func")) != NULL) {
+       ch = doc_following(focus, m);
+       if (ch == WEOF)
+               return Efail;
+       if ((attr = pane_mark_attr(focus, m, "markup:func")) != NULL) {
                /* An alternate function handles this line */
-               ret = call_comm(attr, focus, ci->comm2, o, m, NULL, ci->num2, pm);
+               ret = call_comm(attr, focus, ci->comm2, o, m, NULL, 0, pm);
                if (ret)
                        return ret;
        }
-       boundary = vmark_at_or_before(focus, m, rl->view, ci->home);
-       if (boundary)
+       boundary = vmark_at_or_before(focus, m, mu->view, ci->home);
+       if (boundary) {
+               if (mark_same(m, boundary))
+                       start_boundary = boundary;
                boundary = vmark_next(boundary);
+       }
+       doc_boundary = call_ret(mark, "doc:get-boundary", focus, 1, m);
+
        buf_init(&b);
+       /* Assert that '<' are not quoted */
+       buf_append(&b, ack);
        call_comm("map-attr", focus, &ar.rtn, 0, m, "start-of-line");
+       if (ar.insert.len) {
+               buf_concat(&b, buf_final(&ar.insert));
+               buf_reinit(&ar.insert);
+       }
        while (1) {
                struct mark *m2;
+               int is_true_eol = 0;
 
                if (o >= 0 && b.len >= o)
                        break;
-               if (pm && mark_same(m, pm))
+
+               if (boundary && mark_ordered_or_same(boundary, m))
+                       break;
+               if (doc_boundary && mark_ordered_or_same(doc_boundary, m))
                        break;
 
+               if (pm && mark_same(m, pm) && pm_offset < 0)
+                       pm_offset = b.len;
+
                if (ar.ast && ar.min_end <= chars) {
                        int depth = find_finished(ar.ast, chars, &ar.min_end);
                        as_pop(&ar, depth);
@@ -324,46 +380,55 @@ DEF_CMD(render_line)
                ar.chars = chars;
                call_comm("doc:get-attr", focus, &ar.fwd, 0, m, "render:", 1);
 
-               /* find all marks "here" - they might be fore or aft */
-               for (m2 = doc_prev_mark_all(m); m2 && mark_same(m, m2);
-                    m2 = doc_prev_mark_all(m2))
-                       call_map_mark(focus, m2, &ar);
-               for (m2 = doc_next_mark_all(m); m2 && mark_same(m, m2);
-                    m2 = doc_next_mark_all(m2))
+               /* find all marks "here". They might get moved when we call map_mark,
+                * so move 'm' among them
+                */
+               mark_step(m, 0);
+               while ((m2 = mark_next(m)) != NULL &&
+                      mark_same(m, m2)) {
+                       mark_to_mark_noref(m, m2);
                        call_map_mark(focus, m2, &ar);
+               }
 
                as_repush(&ar, &b);
 
                if (o >= 0 && b.len >= o)
                        break;
 
-               ch = mark_next_pane(focus, m);
+               if (ar.insert.len) {
+                       buf_concat(&b, buf_final(&ar.insert));
+                       buf_reinit(&ar.insert);
+               }
+
+               ch = doc_next(focus, m);
                if (ch == WEOF)
                        break;
+
                if (!oneline && is_eol(ch)) {
-                       add_newline = 1;
-                       if (ch == '\v' && b.len > 0)
-                               mark_prev_pane(focus, m);
-                       break;
+                       doc_prev(focus, m);
+                       is_true_eol = is_render_eol(ch, focus, m);
+                       doc_next(focus, m);
                }
-               if (boundary && boundary->seq <= m->seq)
+               if (is_true_eol) {
+                       add_newline = 1;
                        break;
-               if (ch == '<') {
-                       if (o >= 0 && b.len+1 >= o) {
-                               mark_prev_pane(focus, m);
-                               break;
-                       }
-                       buf_append(&b, '<');
                }
-               if (ch < ' ' && ch != '\t' && (oneline || !is_eol(ch))) {
-                       buf_concat(&b, "<fg:red>^");
+               chars++;
+               if (ch == '\r' && noret) {
+                       /* do nothing */
+               } else if (ch < ' ' && ch != '\t') {
+                       buf_concat(&b, SOH "fg:red" STX "^");
                        buf_append(&b, '@' + ch);
-                       buf_concat(&b, "</>");
+                       buf_concat(&b, ETX);
                } else if (ch == 0x7f) {
-                       buf_concat(&b, "<fg:red>^?</>");
+                       buf_concat(&b, SOH "fg:red" STX "^?" ETX);
+               } else if (ch >= 0x80 && iswcntrl(ch)) {
+                       /* Extra unicode control */
+                       buf_concat(&b, SOH "fg:magenta" STX "^");
+                       buf_append(&b, 96 + (ch & 0x1f));
+                       buf_concat(&b, ETX);
                } else
                        buf_append(&b, ch);
-               chars++;
        }
        if (add_newline && want_vis_newline(ar.ast))
                buf_concat(&b, "↩");
@@ -374,78 +439,79 @@ DEF_CMD(render_line)
        if (add_newline) {
                if (o >= 0 && b.len >= o)
                        /* skip the newline */
-                       mark_prev_pane(focus, m);
+                       doc_prev(focus, m);
                else
                        buf_append(&b, '\n');
        }
 
-       ret = comm_call(ci->comm2, "callback:render", focus, 0, NULL,
+       if (start_boundary && chars < LARGE_LINE - 5)
+               /* This boundary is no longer well-placed. */
+               mark_free(start_boundary);
+
+       mark_free(doc_boundary);
+
+       if (pm && pm_offset < 0)
+               pm_offset = b.len;
+
+       ret = comm_call(ci->comm2, "callback:render", focus, pm_offset, NULL,
                        buf_final(&b));
        free(b.b);
-       return ret;
+       free(ar.insert.b);
+       return ret ?: 1;
 }
 
-DEF_LOOKUP_CMD(renderline_handle, rl_map);
+DEF_LOOKUP_CMD(markup_handle, mu_map);
 
-static struct pane *do_renderline_attach(struct pane *p safe)
+static struct pane *do_markup_attach(struct pane *p safe)
 {
        struct pane *ret;
-       struct rl_info *rl = calloc(1, sizeof(*rl));
+       struct mu_info *mu;
 
-       ret = pane_register(p, 0, &renderline_handle.c, rl);
-       rl->view = home_call(p, "doc:add-view", ret) - 1;
+       ret = pane_register(p, 0, &markup_handle.c);
+       if (!ret)
+               return NULL;
+       mu = ret->data;
+       mu->view = home_call(p, "doc:add-view", ret) - 1;
 
        return ret;
 }
 
-DEF_CMD(renderline_attach)
+DEF_CMD(markup_attach)
 {
        struct pane *ret;
 
-       ret = do_renderline_attach(ci->focus);
+       ret = do_markup_attach(ci->focus);
        if (!ret)
                return Efail;
        return comm_call(ci->comm2, "callback:attach", ret);
 }
 
-DEF_CMD(rl_clone)
+DEF_CMD(mu_clone)
 {
        struct pane *parent = ci->focus;
-       struct pane *child = do_renderline_attach(parent);
+       struct pane *child = do_markup_attach(parent);
        pane_clone_children(ci->home, child);
        return 1;
 }
 
-DEF_CMD(rl_clip)
+DEF_CMD(mu_clip)
 {
-       struct rl_info *rl = ci->home->data;
+       struct mu_info *mu = ci->home->data;
 
-       marks_clip(ci->home, ci->mark, ci->mark2, rl->view, ci->home);
-       return 0;
-}
-
-DEF_CMD(rl_close)
-{
-       struct pane *p = ci->home;
-       struct rl_info *rl = p->data;
-       struct mark *m;
-       while ((m = vmark_first(p, rl->view, p)) != NULL)
-               mark_free(m);
-       call("doc:del-view", p, rl->view);
-       return 0;
+       marks_clip(ci->home, ci->mark, ci->mark2,
+                  mu->view, ci->home, !!ci->num);
+       return Efallthrough;
 }
 
 void edlib_init(struct pane *ed safe)
 {
-       rl_map = key_alloc();
+       mu_map = key_alloc();
 
-       key_add(rl_map, "doc:render-line", &render_line);
-       key_add(rl_map, "doc:render-line-prev", &render_prev);
-       key_add(rl_map, "Clone", &rl_clone);
-       key_add(rl_map, "Close", &rl_close);
-       key_add(rl_map, "Free", &edlib_do_free);
-       key_add(rl_map, "Notify:clip", &rl_clip);
+       key_add(mu_map, "doc:render-line", &render_line);
+       key_add(mu_map, "doc:render-line-prev", &render_prev);
+       key_add(mu_map, "Clone", &mu_clone);
+       key_add(mu_map, "Notify:clip", &mu_clip);
 
-       call_comm("global-set-command", ed, &renderline_attach,
+       call_comm("global-set-command", ed, &markup_attach,
                  0, NULL, "attach-markup");
 }