2 * Copyright Neil Brown ©2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * rangetrack: track ranges of a document which have been processed
6 * in some why, such a spell-check or syntax-highlight or other
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
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
22 #define PANE_DATA_TYPE struct rangetrack_data
25 struct rangetrack_data {
32 #include "core-pane.h"
34 static struct rci *find_set(const struct cmd_info *ci safe)
36 struct rangetrack_data *rtd = ci->home->data;
39 for (i = rtd->info; i; i = i->next) {
40 if (ci->str && strcmp(ci->str, i->set) == 0)
46 static void add_set(struct pane *home safe, const char *set safe)
48 struct rangetrack_data *rtd = home->data;
53 i->view = call("doc:add-view", home) - 1;
58 DEF_CMD_CLOSED(rangetrack_close)
60 struct rangetrack_data *rtd = ci->home->data;
63 while ((i = rtd->info) != NULL) {
71 DEF_CMD(rangetrack_new)
73 struct rci *i = find_set(ci);
79 add_set(ci->home, ci->str);
83 DEF_CMD(rangetrack_add)
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;
93 /* Testing if configured already */
95 m1 = vmark_at_or_before(ci->home, start, i->view, ci->home);
96 if (m1 && attr_find(m1->attrs, "start"))
98 else if (m1 && mark_same(m1, start))
99 /* m1 is an end-of-range. Can move m1 down to cover range */
102 /* Must create a new mark, or move a later mark up */
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 */
110 /* end not in rnage, must create mark or move
115 /* If m2, then move it backwards - no need to create */
117 /* no overlaps, create a new region */
118 m1 = vmark_new(ci->home, i->view, ci->home);
121 mark_to_mark(m1, start);
122 m2 = vmark_new(ci->home, i->view, ci->home);
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 */
130 while (m && mark_ordered_or_same(m, end)) {
134 mark_to_mark(m1, end);
135 } else if (!m1 && m2) {
136 /* Can move m2 up to start, removing things */
138 while (m && mark_ordered_or_same(start, m)) {
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)) {
155 DEF_CMD(rangetrack_clear)
157 struct rci *i = find_set(ci);
158 struct mark *start = ci->mark;
159 struct mark *end = ci->mark2;
160 struct mark *m1, *m2;
164 if (!start || !end) {
165 start = vmark_first(ci->home, i->view, ci->home);
166 end = vmark_last(ci->home, i->view, ci->home);
171 m1 = vmark_at_or_before(ci->home, start, i->view, ci->home);
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.
181 m1 = vmark_first(ci->home, i->view, ci->home);
182 if (!m1 || mark_ordered_or_same(end, m1))
183 /* Nothing to remove */
186 /* From m1 to start are in a range and should stay
187 * there. Split the range from 'm1' at 'start'
189 m1 = vmark_new(ci->home, i->view, ci->home);
192 mark_to_mark(m1, start);
193 m1 = mark_dup_view(m1);
194 /* Ensure this m1 is after the previous one */
196 attr_set_str(&m1->attrs, "start", "yes");
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 */
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);
209 mark_to_mark(m2, end);
210 attr_set_str(&m2->attrs, "start", "yes");
211 m2 = mark_dup_view(m2);
216 /* m2 is now the end of an active section that needs to bie discarded */
217 while (m1 && mark_ordered_not_same(m1, m2)) {
223 call(strconcat(ci->home, "doc:notify:rangetrack:recheck-", i->set),
228 DEF_CMD(rangetrack_choose)
230 struct rci *i = find_set(ci);
231 struct mark *start = ci->mark;
232 struct mark *end = ci->mark2;
233 struct mark *m1, *m2;
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 */
245 /* m1 is in-range, move it forward */
248 mark_to_mark(start, m1);
251 /* Should be impossible */
255 /* Start is before all ranges */
256 m2 = vmark_first(ci->home, i->view, ci->home);
258 if (m2 && mark_ordered_not_same(m2, end))
259 mark_to_mark(end, m2);
263 static struct map *rangetrack_map safe;
264 DEF_LOOKUP_CMD(rangetrack_handle, rangetrack_map);
266 DEF_CMD(rangetrack_attach)
268 struct pane *doc = call_ret(pane, "doc:get-doc", ci->focus);
269 const char *set = ci->str;
276 if (call("doc:notify:rangetrack:new", ci->focus, 0, NULL, set) > 0)
278 p = pane_register(doc, 0, &rangetrack_handle.c);
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");
289 void edlib_init(struct pane *ed safe)
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);