2 * Copyright Neil Brown ©2015 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * line/word/char count.
7 * This module can be attached to a Document to count lines/words/chars.
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.
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.
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)
38 /* if 'end' is NULL, go all the way to EOF */
46 m = mark_dup(start, !add_marks);
51 while ((end == NULL || (mark_ordered_not_same_pane(p, m, end))) &&
52 (ch = mark_next_pane(p, m)) != WEOF) {
56 if (!inword && (iswprint(ch) && !iswspace(ch))) {
59 } else if (inword && !(iswprint(ch) && !iswspace(ch)))
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);
71 lines = words = chars = 0;
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);
86 static int need_recalc(struct pane *p, struct mark *m)
92 if (!attr_find(*mark_attr(m), "lines"))
95 next = doc_next_mark_view(m);
98 if (doc_prior_pane(p, next) == '\n' &&
99 attr_find_int(*mark_attr(next), "lines") > 10)
101 /* discard next - we'll find or create another */
108 static void count_calculate(struct pane *p safe,
109 struct mark *start, struct mark *end,
112 int lines, words, chars, l, w, c;
115 m = vmark_first(p, type);
117 /* No marks yet, let's make some */
118 m = vmark_new(p, type);
121 do_count(p, m, NULL, &l, &w, &c, 1);
123 if (doc_prior_pane(p, m) != WEOF) {
124 /* no mark at start of file */
125 m2 = vmark_new(p, type);
128 do_count(p, m2, m, &l, &w, &c, 1);
133 /* find the first mark that isn't before 'start', and count
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);
142 m = doc_next_mark_view(m);
145 /* fell off the end, just count directly */
146 do_count(p, start, end, &lines, &words, &chars, 0);
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);
154 /* 'm' is not before 'start', it might be after.
155 * if 'm' is not before 'end' either, just count from
158 if (end && !mark_ordered(m, end)) {
159 do_count(p, start?:m, end, &lines, &words, &chars, 0);
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'.
167 if (!start || mark_same_pane(p, m, start))
168 lines = words = chars = 0;
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");
178 if (need_recalc(p, m))
179 do_count(p, m, doc_next_mark_view(m), &l, &w, &c, 1);
181 /* m is the last mark before 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);
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);
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);
205 DEF_CMD(handle_count_lines)
207 struct pane *p = ci->home;
208 struct pane *d = ci->focus;
209 struct count_info *cli = p->data;
211 if (strcmp(ci->key, "Notify:Close") == 0) {
213 while ((m = vmark_first(d, cli->view_num)) != NULL)
215 doc_del_view(d, cli->view_num);
222 if (strcmp(ci->key, "Notify:Replace") == 0) {
226 end = vmark_at_or_before(d, ci->mark, cli->view_num);
228 attr_del(mark_attr(end), "lines");
229 attr_del(mark_attr(end), "words");
230 attr_del(mark_attr(end), "chars");
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);
238 if (strcmp(ci->key, "Notify:doc:CountLines") == 0) {
239 /* Option mark is "mark2" as "mark" get the "point" */
241 pane_add_notify(p, d, "Notify:Close");
242 count_calculate(d, NULL, ci->mark2, cli->view_num);
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;
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,
262 call3("Notify:doc:CountLines", ci->focus, 1, ci->mark);
265 call7("Notify:doc:CountLines", ci->focus, 0, NULL, NULL,
268 call7("Notify:doc:CountLines", ci->focus, 0, NULL, NULL,
273 void edlib_init(struct pane *ed safe)
275 call_comm("global-set-command", ed, 0, NULL, "CountLines",