word-counter.el 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. ;; word-counter.el: --- Count words in the buffer and in region
  2. ;; Time-stamp: "2002-04-18 13:52:05 drs"
  3. ;; Version: 0.2
  4. ;; $Revision:
  5. ;; $Id:
  6. ;; $Source:
  7. ;; Author: Dr. Rafael Sepúlveda. <drs@gnulinux.org.mx>
  8. ;; Mantainer: Dr. Rafael Sepúlveda. <drs@gnulinux.org.mx>
  9. ;; Copyright (C) 2001-2002, Dr. Rafael Sepúlveda <drs@gnulinux.org.mx>
  10. ;; This program is free software; you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation.
  13. ;; This program is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with this program; if not, write to the Free Software Foundation,
  19. ;; Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  20. ;;; Commentary:
  21. ;; This file has all the functions that create a word counter. It has an
  22. ;; interactive word-counter and can display in the modeline the number of
  23. ;; words in the buffer and in the current region, plus more details.
  24. ;;; Usage:
  25. ;; To use the word counter, several steps have to be done. The
  26. ;; `word-counter.el' has to be in the load-path; let's say you'll put it in
  27. ;; `~/etc/emacs/elisp/word-counter.el', so your load path should be (suposing
  28. ;; that this is your only change to load-path):
  29. ;; (setq load-path (cons "~/etc/emacs/elisp" load-path))
  30. ;;
  31. ;; and also you can add this snippet to your `.emacs' to load and configure the
  32. ;; word-counter:
  33. ;; -----
  34. ;; (require 'word-counter) ;load the word counter
  35. ;;
  36. ;; ;; run word counter after each command (including key presses), the
  37. ;; ;; word counter code, will know when exactly to run and don't overload
  38. ;; ;; (too much) the computer.
  39. ;; (add-hook 'post-command-hook 'word-count)
  40. ;;
  41. ;; ;; set limit count to 10,000 words in every major mode; set limit to
  42. ;; ;; 50,000 words in text-mode.
  43. ;; (setq wc-max-words-alist
  44. ;; '((text-mode . 50000)
  45. ;; (t . 10000)))
  46. ;;
  47. ;; ;; toggle on the word-counter on every mode, except for `eshell'.
  48. ;; (setq wc-toggle-mode-alist
  49. ;; '((eshell-mode . nil)
  50. ;; (t . t)))
  51. ;; -----
  52. ;;
  53. ;; Finally you have to add a variable named `word-counter' to the mode-line
  54. ;; so it can be displayed there. If you use the default mode-line, use this
  55. ;; in your `.emacs':
  56. ;; -----
  57. ;; (setq default-mode-line-format
  58. ;; (list "-"
  59. ;; 'mode-line-mule-info
  60. ;; 'mode-line-modified
  61. ;; 'mode-line-frame-identification
  62. ;; 'mode-line-buffer-identification
  63. ;; " "
  64. ;; 'global-mode-string
  65. ;; " %[("
  66. ;; 'mode-name
  67. ;; 'mode-line-process
  68. ;; 'minor-mode-alist
  69. ;; "%n"
  70. ;; ")%]--"
  71. ;; '(which-func-mode ("" which-func-format "--"))
  72. ;; '(line-number-mode "L%l--")
  73. ;; '(column-number-mode "C%c--")
  74. ;; 'word-counter ;this is the only change.
  75. ;; '(-3 . "%p")
  76. ;; "-%-"))
  77. ;; (setq mode-line-format default-mode-line-format)
  78. ;; -----
  79. ;;; TODO:
  80. ;; - Valorate if a fix-point-goal-counter will be needed. (not point-min,
  81. ;; and not in a region; mainly to exclude a header or footer of
  82. ;; the buffer).
  83. ;; - Check why the hooks in html-mode (psgml) stop updating of the
  84. ;; mode-line counter.
  85. ;; - Check why the buffer doesn't make an update if called from a command
  86. ;; in Eshell until a key is pressed (Maybe Eshell bug).
  87. ;; - See if using a function eval instead of mayor modes in
  88. ;; `wc-max-words-alist', `wc-regexp-alist', `wc-toggle-mode-alist'
  89. ;; will be useful.
  90. ;;; Code:
  91. ;; User-definable Variables
  92. (defvar wc-max-words-alist
  93. '((t . 10000))
  94. "Define a limit for Word counter depending on the major mode.
  95. This limit is used so that computation time takes not too long.
  96. The bigger the buffer is, the longer the word count will take.
  97. The alist is formed like this:
  98. (MAJOR-MODE . MAX-WORDS)
  99. MAJOR-MODE: The name of the selected major mode.
  100. MAX-WORDS: Integer that defines the maximum number of words before
  101. word count stop the count.
  102. If word count is bigger than MAX-WORDS, the count will stop. This
  103. limit is only for a non-interactive call. Interactive calls count all
  104. words without any overflow limit.")
  105. (defvar wc-max-words-local nil
  106. "Define a limit for word counter in the local buffer.
  107. If word count is bigger than INTEGER, stop the count. Prevent overflow.
  108. This limit is only for a non-interactive call. Interactive calls count
  109. all words without any overflow limit.
  110. If this variable is set, `wc-max-words-alist' will be ignored on that
  111. buffer.")
  112. (make-variable-buffer-local 'wc-max-words-local)
  113. (defvar wc-reached-goal-function
  114. (function
  115. (lambda ()
  116. (beep)))
  117. "Variable that runs this SEXP when goal-words limit is reached.
  118. If nil, only the mark '!' in the minibuffer will be shown when the
  119. limit is reached or passed.
  120. To set the goal words (buffer-local) use `wc-set-goal-words' interactive
  121. function.")
  122. (defvar wc-regexp-alist
  123. '((t . "\\w+\\W*"))
  124. "Regexp that match words on a given major mode.
  125. This is an alist so it can define a different regexp for any given
  126. major mode, if a different criteria for 'word' is needed.")
  127. (defvar wc-regexp-local nil
  128. "*Regexp that match words in the local buffer.
  129. This buffer-local variable can hold a regexp definition different for
  130. that buffer without considering the major mode used.
  131. If this variable is set, `wc-regexp-alist' will be ignored on that
  132. buffer.")
  133. (make-variable-buffer-local 'wc-regexp-local)
  134. (defvar wc-toggle-mode-alist
  135. '((t . t))
  136. "List major modes that will activate or deactivate the word counter.
  137. The alist is formed like this:
  138. (MAJOR-MODE . t/nil)
  139. MAJOR-MODE: the name of the selected mayor mode.
  140. t/nil: 't' will activate the counter on that mode; 'nil' will deactivate
  141. the counter on that mode.
  142. If the last line is '(t . t)' it means that the counter will activate
  143. on every not-listed mode. If '(t . nil)' the counter will deactivate
  144. the counter on every not-listed mode.")
  145. ;;; No user costumizations beyond this point.
  146. ;; Control Variables
  147. (defvar last-buffer-modified-tick -1
  148. "Check if buffer needs a 'refresh' of the word count.
  149. Used to prevent the word count on every keystroke and to force an update
  150. in the modeline (usually after a region word count).
  151. The variable's value '-1' is only for convention; uses an integer as the
  152. default value type; uses a negative number to avoid any conflict with a
  153. possible value and to avoid any conflict using 'nil'.")
  154. (make-variable-buffer-local 'last-buffer-modified-tick)
  155. (defvar word-counter nil
  156. "Show in each buffer it's own word count.")
  157. (make-variable-buffer-local 'word-counter)
  158. (defvar wc-goal-words nil
  159. "Maximum number of words in the buffer before an alert is made.
  160. This variable is set by the `wc-set-goal-words' function.")
  161. (make-variable-buffer-local 'wc-goal-words)
  162. (defvar wc-words-in-buffer-flag nil
  163. "Non-nil means display current buffer word count.")
  164. (make-variable-buffer-local 'wc-words-in-buffer-flag)
  165. (defvar wc-words-in-region-flag nil
  166. "Non-nil means display current region word count.")
  167. (make-variable-buffer-local 'wc-words-in-region-flag)
  168. (defvar wc-time-control 0
  169. "Don't run the word counter less than a second from the last count.
  170. This way Emacs doesn't have to evaluate the word counter too often;
  171. like when you keep pressed a key.")
  172. (make-variable-buffer-local 'word-time-control)
  173. ;; Functions
  174. ;;; Main function.
  175. (defun word-count (&optional force)
  176. "Count the number of words in a buffer and region.
  177. Count words in buffer if region is not set; if region is set, then
  178. count the words in both the region and the buffer.
  179. If FORCE is non-nil then force the word count overriding
  180. `wc-time-control' variable (only used in non-interactive calls)."
  181. (interactive)
  182. (let ((overflow ;set the overflow limit
  183. (or
  184. wc-max-words-local
  185. (cond ((assq major-mode wc-max-words-alist)
  186. (cdr (assq major-mode wc-max-words-alist)))
  187. ((assq t wc-max-words-alist)
  188. (cdr (assq t wc-max-words-alist))))));)
  189. (regexp ;set the regexp
  190. (or wc-regexp-local
  191. (cond ((assq major-mode wc-regexp-alist)
  192. (cdr (assq major-mode wc-regexp-alist)))
  193. ((assq t wc-regexp-alist)
  194. (cdr (assq t wc-regexp-alist)))))))
  195. (if (interactive-p)
  196. (word-count-interactive regexp)
  197. (word-count-non-interactive regexp overflow force))))
  198. (defun word-count-interactive (regexp)
  199. "Report the word count on the echo area.
  200. This function is executed only when 'word-count' is called
  201. interactively.
  202. REGEXP: Regular expression used to match words."
  203. (let ((region-message
  204. (if mark-active
  205. "Region: %d words. "
  206. ""))
  207. (buffer-message
  208. "Buffer: %d words.")
  209. (goal-message
  210. (if wc-goal-words
  211. (if (> wc-words-in-buffer-flag wc-goal-words)
  212. " Goal count: %d Exceded!"
  213. " Goal count: %d.")
  214. "")))
  215. (message "Counting words, please wait...")
  216. (message (concat region-message buffer-message goal-message)
  217. (if (not (zerop (length region-message)))
  218. (setq wc-words-in-region-flag
  219. (wc-region (region-beginning) (region-end) regexp nil))
  220. (setq wc-words-in-buffer-flag
  221. (wc-region (point-min) (point-max) regexp nil)))
  222. (if (not (zerop (length region-message)))
  223. (setq wc-words-in-buffer-flag
  224. (wc-region (point-min) (point-max) regexp nil))
  225. wc-goal-words)
  226. wc-goal-words)))
  227. (defun word-count-non-interactive (regexp overflow force)
  228. "Report the word count on the modeline.
  229. This function is executed only when 'word-count' is called
  230. non-interactively.
  231. REGEXP: Regexp used to match words in the buffer.
  232. OVERFLOW: Integer that defines the maximum number of words before
  233. word count stop the count.
  234. FORCE: Force the counter to run."
  235. (let ((activate-counter ;see if word-counter will run on this major-mode.
  236. (cond ((assq major-mode wc-toggle-mode-alist)
  237. (cdr (assq major-mode wc-toggle-mode-alist)))
  238. ((assq t wc-toggle-mode-alist)
  239. (cdr (assq t wc-toggle-mode-alist)))))
  240. (wc-format-string ;set the minibuffer string
  241. (if mark-active
  242. "%sW%s/%s--"
  243. "%sW%s--")))
  244. (when
  245. (or force ;if non-nil, force update of counter
  246. (and activate-counter ;counter will run, depending on `wc-toggle-mode-alist'
  247. (not (active-minibuffer-window)) ;don't count words on minibuffer
  248. (or mark-active
  249. (and
  250. (not (= last-buffer-modified-tick ;if buffer has been modified
  251. (buffer-modified-tick)))
  252. (< wc-time-control (cadr (current-time)))) ;don't count too often
  253. (= last-buffer-modified-tick -1)))) ; eval 't' when quit from region
  254. (if mark-active
  255. (progn
  256. (setq wc-words-in-region-flag
  257. (wc-region (region-beginning) (region-end) regexp overflow))
  258. (wc-show wc-words-in-buffer-flag
  259. wc-words-in-region-flag
  260. wc-format-string
  261. wc-goal-words)
  262. (setq last-buffer-modified-tick -1))
  263. (progn
  264. (setq wc-words-in-buffer-flag
  265. (wc-region (point-min) (point-max) regexp overflow))
  266. (wc-show wc-words-in-buffer-flag
  267. wc-words-in-region-flag
  268. wc-format-string
  269. wc-goal-words)
  270. (setq last-buffer-modified-tick (buffer-modified-tick))))
  271. (setq wc-time-control (cadr (current-time))))))
  272. ;;; Update interactively the `wc-goal-words' variable.
  273. (defun wc-set-goal-words ()
  274. "Set the maximum number of words in the buffer before an alert is made.
  275. The number asked in the minibuffer has to be an integer.
  276. If the value entered is not a number, zero is asumed.
  277. If the value entered is a number but not an integer, only the integer
  278. part is used.
  279. If the value entered is a negative integer, the goal counter is
  280. deactivated."
  281. (interactive)
  282. (let ((goal ;set the goal word number.
  283. (string-to-number
  284. (read-string
  285. (concat
  286. (and wc-goal-words (format "Actual goal: %d. " wc-goal-words))
  287. "New Goal: (a negative integer deactivates goal count) ")))))
  288. (cond ((< goal 0)
  289. (message "Word goal counter deactivated!"
  290. (setq wc-goal-words nil)))
  291. ((= goal 0) ;if given a non-numeric value, '0' is assumed.
  292. (if (y-or-n-p (format "0 is not recomended. Are you sure? " goal))
  293. (message "New goal set to %d."
  294. (setq wc-goal-words goal))
  295. (wc-set-goal-words)))
  296. (t
  297. (message "New goal set to %d."
  298. (setq wc-goal-words goal))))
  299. (word-count t)))
  300. ;;; Update the `word-counter' variable info; will be updated in the modeline.
  301. (defun wc-show (buffer-count region-count format-string goal-words)
  302. "Set the correct values in the `word-counter' variable.
  303. All parameters (BUFFER-COUNT, REGION-COUNT, FORMAT-STRING and GOAL-WORDS)
  304. are passed from other functions.
  305. This variable will be displayed in the mode-line."
  306. (let ((first (if mark-active
  307. (or region-count "??")
  308. (or buffer-count "??")))
  309. (second (if mark-active
  310. (or buffer-count "??")))
  311. (goal-mark (if (and goal-words (> buffer-count goal-words))
  312. (progn
  313. (funcall wc-reached-goal-function)
  314. "!")
  315. "")))
  316. (setq word-counter
  317. (format
  318. format-string
  319. goal-mark
  320. first
  321. second))
  322. (force-mode-line-update)))
  323. ;;; Actual word counter.
  324. (defun wc-region (beg end regexp overflow)
  325. "Count the number of words between BEG and END.
  326. The argument REGEXP is the actual regular expresion used in this buffer.
  327. The argument OVERFLOW marks the maximum number of words found before
  328. stopping, to prevent an overflow."
  329. (let ((count 0))
  330. (save-excursion
  331. (goto-char beg)
  332. (while (and (< (point) end)
  333. (re-search-forward regexp end t)
  334. (if overflow
  335. (or (< count overflow) ;return 'nil' if overflow.
  336. (progn (setq count nil) nil))
  337. t))
  338. (setq count (1+ count))))
  339. count))
  340. (provide 'word-counter)
  341. ;;; word-counter.el ends here