about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--init.el3
-rw-r--r--lisp/acdw-org.el142
2 files changed, 59 insertions, 86 deletions
diff --git a/init.el b/init.el index b8584b6..3bec384 100644 --- a/init.el +++ b/init.el
@@ -973,7 +973,8 @@ if ripgrep is installed, otherwise `consult-grep'."
973(setup (:straight simple-modeline) 973(setup (:straight simple-modeline)
974 (setup (:straight minions)) 974 (setup (:straight minions))
975 (:option 975 (:option
976 ;; (prepend acdw-modeline/word-count-modes) '(org-mode . #'acdw-org/word-count) 976 ;; `acdw-org/count-words' is too slow to use in the modeline.
977 ;; (prepend acdw-modeline/word-count-modes) '(org-mode . acdw-org/count-words)
977 simple-modeline-segments '((acdw-modeline/modified 978 simple-modeline-segments '((acdw-modeline/modified
978 acdw-modeline/buffer-name 979 acdw-modeline/buffer-name
979 acdw-modeline/vc-branch 980 acdw-modeline/vc-branch
diff --git a/lisp/acdw-org.el b/lisp/acdw-org.el index 3f0c4ea..7e68712 100644 --- a/lisp/acdw-org.el +++ b/lisp/acdw-org.el
@@ -243,91 +243,63 @@ the deletion might narrow the column."
243 (org-table-copy-down n) 243 (org-table-copy-down n)
244 (acdw-org/return-dwim n))) 244 (acdw-org/return-dwim n)))
245 245
246(defun acdw-org/word-count (beg end 246(defun acdw-org/count-words (start end)
247 &optional count-latex-macro-args? 247 "Count words between START and END, respecting `org-mode' conventions."
248 count-footnotes?) 248 (interactive (list nil nil))
249 "Report the number of words in the Org mode buffer or selected region. 249 (cond ((not (called-interactively-p 'any))
250Ignores: 250 (let ((words 0))
251- comments 251 (save-excursion
252- tables 252 (save-restriction
253- source code blocks (#+BEGIN_SRC ... #+END_SRC, and inline blocks) 253 (narrow-to-region start end)
254- hyperlinks (but does count words in hyperlink descriptions) 254 (goto-char (point-min))
255- tags, priorities, and TODO keywords in headers 255 (while (< (point) (point-max))
256- sections tagged as 'not for export'. 256 (cond
257 ;; Ignore comments
258 ((or (org-at-comment-p)
259 (org-in-commented-heading-p)) nil)
260 ;; Ignore tables
261 ((org-at-table-p) nil)
262 ;; Ignore hyperlinks, but count the descriptions
263 ((looking-at org-bracket-link-analytic-regexp)
264 (when-let ((desc (match-string-no-properties 5)))
265 (save-match-data
266 (setq words (+ words
267 (length (remove ""
268 (org-split-string
269 desc "\\W")))))))
270 (goto-char (match-end 0)))
271 ;; Ignore source code blocks
272 ((org-in-src-block-p) nil)
273 ;; Ignore footnotes
274 ((or (org-footnote-at-definition-p)
275 (org-footnote-at-reference-p))
276 nil)
277 ;; else... check the context
278 (t (let ((contexts (org-context)))
279 (cond
280 ;; Ignore tags, TODO keywords, etc.
281 ((or (assoc :todo-keyword contexts)
282 (assoc :priority contexts)
283 (assoc :keyword contexts)
284 (assoc :checkbox contexts))
285 nil)
286 ;; Ignore sections tagged :no-export
287 ((assoc :tags contexts)
288 (if (intersection (org-get-tags-at)
289 org-export-exclude-tags
290 :test 'equal)
291 (org-forward-same-level 1)
292 nil))
293 ;; else... count the word
294 (t (setq words (1+ words)))))))
295 (re-search-forward "\\w+\\W*")))
296 words)))
297 ((use-region-p)
298 (message "%d words in region"
299 (acdw-org/count-words (region-beginning) (region-end))))
300 (t
301 (message "%d words in buffer"
302 (acdw-org/count-words (point-min) (point-max))))))
257 303
258The text of footnote definitions is ignored, unless the optional argument
259COUNT-FOOTNOTES? is non-nil.
260
261If the optional argument COUNT-LATEX-MACRO-ARGS? is non-nil, the word count
262includes LaTeX macro arguments (the material between {curly braces}).
263Otherwise, and by default, every LaTeX macro counts as 1 word regardless
264of its arguments."
265 (interactive "r")
266 (unless mark-active
267 (setf beg (point-min)
268 end (point-max)))
269 (let ((wc 0)
270 (latex-macro-regexp "\\\\[A-Za-z]+\\(\\[[^]]*\\]\\|\\){\\([^}]*\\)}"))
271 (save-excursion
272 (goto-char beg)
273 (while (< (point) end)
274 (cond
275 ;; Ignore comments.
276 ((or (org-in-commented-line) (org-at-table-p))
277 nil)
278 ;; Ignore hyperlinks. But if link has a description, count
279 ;; the words within the description.
280 ((looking-at org-bracket-link-analytic-regexp)
281 (when (match-string-no-properties 5)
282 (let ((desc (match-string-no-properties 5)))
283 (save-match-data
284 (incf wc (length (remove "" (org-split-string
285 desc "\\W")))))))
286 (goto-char (match-end 0)))
287 ((looking-at org-any-link-re)
288 (goto-char (match-end 0)))
289 ;; Ignore source code blocks.
290 ((org-in-regexps-block-p "^#\\+BEGIN_SRC\\W" "^#\\+END_SRC\\W")
291 nil)
292 ;; Ignore inline source blocks, counting them as 1 word.
293 ((save-excursion
294 (backward-char)
295 (looking-at org-babel-inline-src-block-regexp))
296 (goto-char (match-end 0))
297 (setf wc (+ 2 wc)))
298 ;; Count latex macros as 1 word, ignoring their arguments.
299 ((save-excursion
300 (backward-char)
301 (looking-at latex-macro-regexp))
302 (goto-char (if count-latex-macro-args?
303 (match-beginning 2)
304 (match-end 0)))
305 (setf wc (+ 2 wc)))
306 ;; Ignore footnotes.
307 ((and (not count-footnotes?)
308 (or (org-footnote-at-definition-p)
309 (org-footnote-at-reference-p)))
310 nil)
311 (t
312 (let ((contexts (org-context)))
313 (cond
314 ;; Ignore tags and TODO keywords, etc.
315 ((or (assoc :todo-keyword contexts)
316 (assoc :priority contexts)
317 (assoc :keyword contexts)
318 (assoc :checkbox contexts))
319 nil)
320 ;; Ignore sections marked with tags that are
321 ;; excluded from export.
322 ((assoc :tags contexts)
323 (if (intersection (org-get-tags-at) org-export-exclude-tags
324 :test 'equal)
325 (org-forward-same-level 1)
326 nil))
327 (t
328 (incf wc))))))
329 (re-search-forward "\\w+\\W*")))
330 (message (format "%d words in %s." wc
331 (if mark-active "region" "buffer")))))
332 304
333(provide 'acdw-org) 305(provide 'acdw-org)