summary refs log tree commit diff stats
path: root/lisp/+org-wc.el
blob: edd88f0a1e1fb8db49f44d3a1a9306ea02c06a28 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
;;; +org-wc.el --- org-wc in the modeline -*- lexical-binding: t; -*-

;;; Commentary:

;;; Code:

(require 'org-wc)
(require '+modeline)
(require 'cl-lib)

(defgroup +org-wc nil
  "Extra fast word-counting in `org-mode'"
  :group 'org-wc
  :group 'org)

(defvar-local +org-wc-word-count nil
  "Running total of words in this buffer.")

(defcustom +org-wc-update-after-funcs '(org-narrow-to-subtree
                                        org-narrow-to-block
                                        org-narrow-to-element
                                        org-capture-narrow)
  "Functions after which to update the word count."
  :type '(repeat function))

(defcustom +org-wc-deletion-idle-timer 0.25
  "Length of time, in seconds, to wait before updating word-count."
  :type 'number)

(defcustom +org-wc-huge-change 5000
  "Number of characters that constitute a \"huge\" insertion."
  :type 'number)

(defcustom +org-wc-huge-buffer 10000
  "Number of words past which we're not going to try to count."
  :type 'number)

(defvar +org-wc-correction -5
  "Number to add to `+org-wc-word-count', for some reason?
`+org-wc-word-count' seems to consistently be off by 5.  Thus
this correction.  (At some point I should correct the underlying
code... probably).")

(defvar-local +org-wc-update-timer nil)

(defun +org-wc-delayed-update (&rest _)
  (if +org-wc-update-timer
      (setq +org-wc-update-timer nil)
    (setq +org-wc-update-timer
          (run-with-idle-timer +org-wc-deletion-idle-timer nil #'+org-wc-update))))

(defun +org-wc-force-update ()
  (interactive)
  (message "Counting words...")
  (when (timerp +org-wc-update-timer)
    (cancel-timer +org-wc-update-timer))
  (+org-wc-update)
  (message "Counting words...done"))

(defun +org-wc-update (&rest _)         ; Needs variadic parameters, since it's advice
  (dlet ((+org-wc-counting t))
    (+org-wc-buffer)
    (force-mode-line-update)
    (setq +org-wc-update-timer nil)))

(defun +org-wc-changed (start end length)
  (+org-wc-delayed-update))

(defun +org-wc-buffer ()
  "Count the words in the buffer."
  (when (and (derived-mode-p 'org-mode)
             (not (eq +org-wc-word-count 'huge)))
    (setq +org-wc-word-count
          (cond
           ((> (count-words (point-min) (point-max))
               +org-wc-huge-buffer)
            'huge)
           (t (org-word-count-aux (point-min) (point-max)))))))

(defvar +org-wc-counting nil
  "Are we currently counting?")

(defun +org-wc-recount-widen (&rest _)
  (when (and (not +org-wc-counting))
    (+org-wc-update)))

(defun +org-wc-modeline ()
  (cond
   ((eq +org-wc-word-count 'huge) "huge")
   (+org-wc-word-count (format " %sw" (max 0 (+ +org-wc-word-count +org-wc-correction))))))

(define-minor-mode +org-wc-mode
  "Count words in `org-mode' buffers in the mode-line."
  :lighter ""
  :keymap (let ((map (make-sparse-keymap)))
            (define-key map (kbd "C-c C-.") #'+org-wc-force-update)
            map)
  (if +org-wc-mode
      (progn                            ; turn on
        (+org-wc-buffer)
        (add-hook 'after-change-functions #'+org-wc-delayed-update nil t)
        (setq-local +modeline-position-function #'+org-wc-modeline)
        (dolist (fn +org-wc-update-after-funcs)
          (advice-add fn :after #'+org-wc-update)))
    (progn                              ; turn off
      (remove-hook 'after-change-functions #'+org-wc-delayed-update t)
      (kill-local-variable '+modeline-position-function)
      (dolist (fn +org-wc-update-after-funcs)
        (advice-remove fn #'+org-wc-update)))))

(provide '+org-wc)
;;; +org-wc.el ends here