]> git.neil.brown.name Git - edlib.git/blob - lib-rangetrack.c
TODO: clean out done items.
[edlib.git] / lib-rangetrack.c
1 /*
2  * Copyright Neil Brown ©2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * rangetrack: track ranges of a document which have been processed
6  * in some why, such a spell-check or syntax-highlight or other
7  * parsing.
8  *
9  * rangetrack will attach a pane to the target document to store
10  * marks and other state.  It can track an arbitrary set of different
11  * range types.
12  *
13  * rangetrack:new : start tracking ranges on 'focus' document.
14  *              str is the name of the range set.
15  * rangetrack:add     : record that mark to mark2 are a valid range
16  * rangetrack:remove  : record that from mark to mark2 are no longer valid
17  * rangetrack:choose  : report a subrange for mark..mark2 which is not
18  *                      currently valid.
19  *
20  */
21
22 #define PANE_DATA_TYPE struct rangetrack_data
23
24 #include "core.h"
25 struct rangetrack_data {
26         struct rci {
27                 const char *set safe;
28                 int view;
29                 struct rci *next;
30         } *info;
31 };
32 #include "core-pane.h"
33
34 static struct rci *find_set(const struct cmd_info *ci safe)
35 {
36         struct rangetrack_data *rtd = ci->home->data;
37         struct rci *i;
38
39         for (i = rtd->info; i; i = i->next) {
40                 if (ci->str && strcmp(ci->str, i->set) == 0)
41                         return i;
42         }
43         return NULL;
44 }
45
46 static void add_set(struct pane *home safe, const char *set safe)
47 {
48         struct rangetrack_data *rtd = home->data;
49         struct rci *i;
50
51         alloc(i, pane);
52         i->set = strdup(set);
53         i->view = call("doc:add-view", home) - 1;
54         i->next = rtd->info;
55         rtd->info = i;
56 }
57
58 DEF_CMD_CLOSED(rangetrack_close)
59 {
60         struct rangetrack_data *rtd = ci->home->data;
61         struct rci *i;
62
63         while ((i = rtd->info) != NULL) {
64                 rtd->info = i->next;
65                 free((void*)i->set);
66                 unalloc(i, pane);
67         }
68         return 1;
69 }
70
71 DEF_CMD(rangetrack_new)
72 {
73         struct rci *i = find_set(ci);
74
75         if (!ci->str)
76                 return Enoarg;
77         if (i)
78                 return Efalse;
79         add_set(ci->home, ci->str);
80         return 1;
81 }
82
83 DEF_CMD(rangetrack_add)
84 {
85         struct rci *i = find_set(ci);
86         struct mark *start = ci->mark;
87         struct mark *end = ci->mark2;
88         struct mark *m, *m1, *m2;
89
90         if (!i)
91                 return Efalse;
92         if (!start || !end)
93                 /* Testing if configured already */
94                 return 1;
95         m1 = vmark_at_or_before(ci->home, start, i->view, ci->home);
96         if (m1 && attr_find(m1->attrs, "start"))
97                 m1 = vmark_next(m1);
98         else if (m1 && mark_same(m1, start))
99                 /* m1 is an end-of-range. Can move m1 down to cover range */
100                 ;
101         else
102                 /* Must create a new mark, or move a later mark up */
103                 m1 = NULL;
104         m2 = vmark_at_or_before(ci->home, end, i->view, ci->home);
105         if (m2 && attr_find(m2->attrs, "start") == NULL) {
106                 if (mark_same(m2, end))
107                         /* Can move the start of this range earlier */
108                         m2 = vmark_prev(m2);
109                 else
110                         /* end not in rnage, must create mark or move
111                          * earlier mark down
112                          */
113                         m2 = NULL;
114         }
115         /* If m2, then move it backwards - no need to create */
116         if (!m1 && !m2) {
117                 /* no overlaps, create a new region */
118                 m1 = vmark_new(ci->home, i->view, ci->home);
119                 if (!m1)
120                         return Efail;
121                 mark_to_mark(m1, start);
122                 m2 = vmark_new(ci->home, i->view, ci->home);
123                 if (!m2)
124                         return Efail;
125                 mark_to_mark(m2, end);
126                 attr_set_str(&m1->attrs, "start", "yes");
127         } else if (m1 && !m2) {
128                 /* Can move m1 dow n to end, removing anything in the way */
129                 m = vmark_next(m1);
130                 while (m && mark_ordered_or_same(m, end)) {
131                         mark_free(m);
132                         m = vmark_next(m1);
133                 }
134                 mark_to_mark(m1, end);
135         } else if (!m1 && m2) {
136                 /* Can move m2 up to start, removing things */
137                 m = vmark_prev(m2);
138                 while (m && mark_ordered_or_same(start, m)) {
139                         mark_free(m);
140                         m = vmark_prev(m2);
141                 }
142                 mark_to_mark(m2, start);
143         } else if (m1 && m2) {
144                 /* Can remove all from m1 to m2 inclusive */
145                 while (m1 && mark_ordered_not_same(m1, m2)) {
146                         m = vmark_next(m1);
147                         mark_free(m1);
148                         m1 = m;
149                 }
150                 mark_free(m2);
151         }
152         return 1;
153 }
154
155 DEF_CMD(rangetrack_clear)
156 {
157         struct rci *i = find_set(ci);
158         struct mark *start = ci->mark;
159         struct mark *end = ci->mark2;
160         struct mark *m1, *m2;
161
162         if (!i)
163                 return Efalse;
164         if (!start || !end) {
165                 start = vmark_first(ci->home, i->view, ci->home);
166                 end = vmark_last(ci->home, i->view, ci->home);
167         }
168         if (!start || !end)
169                 return 1;
170
171         m1 = vmark_at_or_before(ci->home, start, i->view, ci->home);
172
173         if (!m1 || attr_find(m1->attrs, "start") == NULL) {
174                 /* Immediately after start is not active, so the
175                  * earlierst we might need to remove is the next
176                  * mark, or possibly the very first mark.
177                  */
178                 if (m1)
179                         m1 = vmark_next(m1);
180                 else
181                         m1 = vmark_first(ci->home, i->view, ci->home);
182                 if (!m1 || mark_ordered_or_same(end, m1))
183                         /* Nothing to remove */
184                         return 1;
185         } else {
186                 /* From m1 to start are in a range and should stay
187                  * there.  Split the range from 'm1' at 'start'
188                  */
189                 m1 = vmark_new(ci->home, i->view, ci->home);
190                 if (!m1)
191                         return Efail;
192                 mark_to_mark(m1, start);
193                 m1 = mark_dup_view(m1);
194                 /* Ensure this m1 is after the previous one */
195                 mark_step(m1, 1);
196                 attr_set_str(&m1->attrs, "start", "yes");
197         }
198         /* m is now the start of an active section that is within
199          * start-end and should be removed */
200         m2 = vmark_at_or_before(ci->home, end, i->view, ci->home);
201         if (m2 && mark_same(m2, end) && attr_find(m2->attrs, "start"))
202                 /* This section is entirely after end, so not interesting */
203                 m2 = vmark_prev(m2);
204         if (m2 && attr_find(m2->attrs, "start")) {
205                 /* end is within an active secion that needs to be split */
206                 m2 = vmark_new(ci->home, i->view, ci->home);
207                 if (!m2)
208                         return Efail;
209                 mark_to_mark(m2, end);
210                 attr_set_str(&m2->attrs, "start", "yes");
211                 m2 = mark_dup_view(m2);
212                 mark_step(m2, 0);
213         }
214         if (!m2)
215                 return 1;
216         /* m2 is now the end of an active section that needs to bie discarded */
217         while (m1 && mark_ordered_not_same(m1, m2)) {
218                 struct mark *m = m1;
219                 m1 = vmark_next(m1);
220                 mark_free(m);
221         }
222         mark_free(m2);
223         call(strconcat(ci->home, "doc:notify:rangetrack:recheck-", i->set),
224              ci->home);
225         return 1;
226 }
227
228 DEF_CMD(rangetrack_choose)
229 {
230         struct rci *i = find_set(ci);
231         struct mark *start = ci->mark;
232         struct mark *end = ci->mark2;
233         struct mark *m1, *m2;
234
235         if (!i)
236                 return Efail;
237         if (!start || !end)
238                 return Enoarg;
239         /* Contract start-end so that none of it is in-range */
240         m1 = vmark_at_or_before(ci->home, start, i->view, ci->home);
241         if (m1 && attr_find(m1->attrs, "start") == NULL)
242                 /* Start is not in-range, end must not exceed m1 */
243                 m2 = vmark_next(m1);
244         else if (m1) {
245                 /* m1 is in-range, move it forward */
246                 m1 = vmark_next(m1);
247                 if (m1) {
248                         mark_to_mark(start, m1);
249                         m2 = vmark_next(m1);
250                 } else {
251                         /* Should be impossible */
252                         m2 = start;
253                 }
254         } else {
255                 /* Start is before all ranges */
256                 m2 = vmark_first(ci->home, i->view, ci->home);
257         }
258         if (m2 && mark_ordered_not_same(m2, end))
259                 mark_to_mark(end, m2);
260         return 1;
261 }
262
263 static struct map *rangetrack_map safe;
264 DEF_LOOKUP_CMD(rangetrack_handle, rangetrack_map);
265
266 DEF_CMD(rangetrack_attach)
267 {
268         struct pane *doc = call_ret(pane, "doc:get-doc", ci->focus);
269         const char *set = ci->str;
270         struct pane *p;
271
272         if (!set)
273                 return Enoarg;
274         if (!doc)
275                 return Efail;
276         if (call("doc:notify:rangetrack:new", ci->focus, 0, NULL, set) > 0)
277                 return 1;
278         p = pane_register(doc, 0, &rangetrack_handle.c);
279         if (!p)
280                 return Efail;
281         pane_add_notify(p, doc, "rangetrack:new");
282         pane_add_notify(p, doc, "rangetrack:add");
283         pane_add_notify(p, doc, "rangetrack:clear");
284         pane_add_notify(p, doc, "rangetrack:choose");
285         add_set(p, set);
286         return 1;
287 }
288
289 void edlib_init(struct pane *ed safe)
290 {
291         call_comm("global-set-command", ed, &rangetrack_attach,
292                   0, NULL, "rangetrack:new");
293         rangetrack_map = key_alloc();
294         key_add(rangetrack_map, "Close", &rangetrack_close);
295         key_add(rangetrack_map, "rangetrack:new", &rangetrack_new);
296         key_add(rangetrack_map, "rangetrack:add", &rangetrack_add);
297         key_add(rangetrack_map, "rangetrack:clear", &rangetrack_clear);
298         key_add(rangetrack_map, "rangetrack:choose", &rangetrack_choose);
299 }