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