;;; +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
                                        org-taskwise-narrow-to-task)
  "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)

(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 ()
  (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 (derived-mode-p 'org-mode)
    (setq +org-wc-word-count
         (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 ()
  (when +org-wc-word-count
    (format " %sw" +org-wc-word-count)))

(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
