]> git.neil.brown.name Git - edlib.git/blob - lib-linecount.c
Remove 'safe' annotation for vmark_new
[edlib.git] / lib-linecount.c
1 /*
2  * Copyright Neil Brown ©2015 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * line/word/char count.
6  *
7  * This module can be attached to a Document to count lines/words/chars.
8  *
9  * It attaches active marks every 50 lines or so and records the
10  * counts between the marks.  These are stored as attributes
11  * 'lines' 'words' 'chars'.
12  * When a change is notified, the attributes are cleared.
13  * When a count is requested, all marks from top-of-file to target
14  * are examined.  If attributes are not present they are calculated.
15  * Then they are summed.
16  * The text from the last active mark at the target is always calculated.
17  *
18  * When recalculating a range, we drop a new mark every 50 lines.
19  * When we find a mark the needs updating, we discard it if previous mark is
20  * closer than 10 lines.
21  */
22
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <wchar.h>
26 #include <wctype.h>
27 #include <string.h>
28
29 #include "core.h"
30
31 struct count_info {
32         int view_num;
33 };
34
35 static void do_count(struct pane *p safe, struct mark *start, struct mark *end,
36                      int *linep safe, int *wordp safe, int *charp safe, int add_marks)
37 {
38         /* if 'end' is NULL, go all the way to EOF */
39         int lines = 0;
40         int words = 0;
41         int chars = 0;
42         int inword = 0;
43         wint_t ch;
44         struct mark *m;
45
46         m = mark_dup(start, !add_marks);
47
48         *linep = 0;
49         *wordp = 0;
50         *charp = 0;
51         while ((end == NULL || (mark_ordered_not_same_pane(p, m, end))) &&
52                (ch = mark_next_pane(p, m)) != WEOF) {
53                 chars += 1;
54                 if (ch == '\n')
55                         lines += 1;
56                 if (!inword && (iswprint(ch) && !iswspace(ch))) {
57                         inword = 1;
58                         words += 1;
59                 } else if (inword && !(iswprint(ch) && !iswspace(ch)))
60                         inword = 0;
61                 if (add_marks && lines >= 50 &&
62                     (end == NULL || (mark_ordered_not_same_pane(p, m, end)))) {
63                         /* leave a mark here and keep going */
64                         attr_set_int(mark_attr(start), "lines", lines);
65                         attr_set_int(mark_attr(start), "words", words);
66                         attr_set_int(mark_attr(start), "chars", chars);
67                         start = m;
68                         *linep += lines;
69                         *wordp += words;
70                         *charp += chars;
71                         lines = words = chars = 0;
72                         m = mark_dup(m, 0);
73                 }
74         }
75         if (add_marks) {
76                 attr_set_int(mark_attr(start), "lines", lines);
77                 attr_set_int(mark_attr(start), "words", words);
78                 attr_set_int(mark_attr(start), "chars", chars);
79         }
80         *linep += lines;
81         *wordp += words;
82         *charp += chars;
83         mark_free(m);
84 }
85
86 static int need_recalc(struct pane *p, struct mark *m)
87 {
88         struct mark *next;
89         int ret = 0;
90         if (!m)
91                 return 1;
92         if (!attr_find(*mark_attr(m), "lines"))
93                 ret = 1;
94         while (1) {
95                 next = doc_next_mark_view(m);
96                 if (!next)
97                         break;
98                 if (doc_prior_pane(p, next) == '\n' &&
99                     attr_find_int(*mark_attr(next), "lines") > 10)
100                         break;
101                 /* discard next - we'll find or create another */
102                 mark_free(next);
103                 ret = 1;
104         }
105         return ret;
106 }
107
108 static void count_calculate(struct pane *p safe,
109                             struct mark *start, struct mark *end,
110                             int type)
111 {
112         int lines, words, chars, l, w, c;
113         struct mark *m, *m2;
114
115         m = vmark_first(p, type);
116         if (m == NULL) {
117                 /* No marks yet, let's make some */
118                 m = vmark_new(p, type);
119                 if (!m)
120                         return;
121                 do_count(p, m, NULL, &l, &w, &c, 1);
122         }
123         if (doc_prior_pane(p, m) != WEOF) {
124                 /* no mark at start of file */
125                 m2 = vmark_new(p, type);
126                 if (!m2)
127                         return;
128                 do_count(p, m2, m, &l, &w, &c, 1);
129                 m = m2;
130         }
131
132         if (start) {
133                 /* find the first mark that isn't before 'start', and count
134                  * from there.
135                  */
136                 while (m && mark_ordered_not_same_pane(p, m, start)) {
137                         /* Force and update to make sure spacing stays sensible */
138                         if (need_recalc(p, m))
139                                 /* need to update this one */
140                                 do_count(p, m, doc_next_mark_view(m), &l, &w, &c, 1);
141
142                         m = doc_next_mark_view(m);
143                 }
144                 if (!m) {
145                         /* fell off the end, just count directly */
146                         do_count(p, start, end, &lines, &words, &chars, 0);
147                         goto done;
148                 }
149         }
150         if (need_recalc(p, m))
151                 /* need to update this one */
152                 do_count(p, m, doc_next_mark_view(m), &l, &w, &c, 1);
153
154         /* 'm' is not before 'start', it might be after.
155          * if 'm' is not before 'end' either, just count from
156          * start to end.
157          */
158         if (end && !mark_ordered(m, end)) {
159                 do_count(p, start?:m, end, &lines, &words, &chars, 0);
160                 goto done;
161         }
162
163         /* OK, 'm' is between 'start' and 'end'.
164          * So count from start to m, then add totals from m and subsequent.
165          * Then count to 'end'.
166          */
167         if (!start || mark_same_pane(p, m, start))
168                 lines = words = chars = 0;
169         else
170                 do_count(p, start, m, &lines, &words, &chars, 0);
171         while ((m2 = doc_next_mark_view(m)) != NULL &&
172                (!end || mark_ordered(m2, end))) {
173                 /* Need everything from m to m2 */
174                 lines += attr_find_int(*mark_attr(m), "lines");
175                 words += attr_find_int(*mark_attr(m), "words");
176                 chars += attr_find_int(*mark_attr(m), "chars");
177                 m = m2;
178                 if (need_recalc(p, m))
179                         do_count(p, m, doc_next_mark_view(m), &l, &w, &c, 1);
180         }
181         /* m is the last mark before end */
182         if (!end) {
183                 lines += attr_find_int(*mark_attr(m), "lines");
184                 words += attr_find_int(*mark_attr(m), "words");
185                 chars += attr_find_int(*mark_attr(m), "chars");
186         } else if (!mark_same_pane(p, m, end)) {
187                 do_count(p, m, end, &l, &w, &c, 0);
188                 lines += l;
189                 words += w;
190                 chars += c;
191         }
192 done:
193         if (end) {
194                 struct attrset **attrs = &end->attrs;
195                 attr_set_int(attrs, "lines", lines);
196                 attr_set_int(attrs, "words", words);
197                 attr_set_int(attrs, "chars", chars);
198         } else {
199                 call5("doc:attr-set", p, lines, NULL, "lines", 1);
200                 call5("doc:attr-set", p, words, NULL, "words", 1);
201                 call5("doc:attr-set", p, chars, NULL, "chars", 1);
202         }
203 }
204
205 DEF_CMD(handle_count_lines)
206 {
207         struct pane *p = ci->home;
208         struct pane *d = ci->focus;
209         struct count_info *cli = p->data;
210
211         if (strcmp(ci->key, "Notify:Close") == 0) {
212                 struct mark *m;
213                 while ((m = vmark_first(d, cli->view_num)) != NULL)
214                         mark_free(m);
215                 doc_del_view(d, cli->view_num);
216                 free(cli);
217                 p->data = NULL;
218                 pane_close(p);
219                 return 1;
220         }
221
222         if (strcmp(ci->key, "Notify:Replace") == 0) {
223                 if (ci->mark) {
224                         struct mark *end;
225
226                         end = vmark_at_or_before(d, ci->mark, cli->view_num);
227                         if (end) {
228                                 attr_del(mark_attr(end), "lines");
229                                 attr_del(mark_attr(end), "words");
230                                 attr_del(mark_attr(end), "chars");
231                         }
232                         call5("doc:attr-set", d, 0, NULL, "lines", 0);
233                         call5("doc:attr-set", d, 0, NULL, "words", 0);
234                         call5("doc:attr-set", d, 0, NULL, "chars", 0);
235                 }
236                 return 1;
237         }
238         if (strcmp(ci->key, "Notify:doc:CountLines") == 0) {
239                 /* Option mark is "mark2" as "mark" get the "point" */
240                 if (ci->numeric)
241                         pane_add_notify(p, d, "Notify:Close");
242                 count_calculate(d, NULL, ci->mark2, cli->view_num);
243                 return 1;
244         }
245         return 0;
246 }
247
248 DEF_CMD(count_lines)
249 {
250         /* FIXME optimise this away most of the time */
251         if (call3("Notify:doc:CountLines", ci->focus, 0, NULL) == 0) {
252                 /* No counter in place, add one */
253                 struct count_info *cli;
254                 struct pane *p;
255
256                 cli = calloc(1, sizeof(*cli));
257                 cli->view_num = doc_add_view(ci->focus);
258                 p = pane_register(NULL, 0, &handle_count_lines, cli, NULL);
259                 call_home(ci->focus, "Request:Notify:Replace", p, 0, NULL, NULL);
260                 call_home(ci->focus, "Request:Notify:doc:CountLines", p,
261                           0, NULL, NULL);
262                 call3("Notify:doc:CountLines", ci->focus, 1, ci->mark);
263         }
264         if (ci->mark)
265                 call7("Notify:doc:CountLines", ci->focus, 0, NULL, NULL,
266                       0, NULL, ci->mark);
267         if (ci->mark2)
268                 call7("Notify:doc:CountLines", ci->focus, 0, NULL, NULL,
269                       0, NULL, ci->mark2);
270         return 1;
271 }
272
273 void edlib_init(struct pane *ed safe)
274 {
275         call_comm("global-set-command", ed, 0, NULL, "CountLines",
276                   0, &count_lines);
277 }