branch: master
commit f4d03a33ebdd5c0a66db890a81aabaf6c3a634c8
Merge: edbdf4e 463d752
Author: Ian Dunn <du...@gnu.org>
Commit: Ian Dunn <du...@gnu.org>

    Merge commit '463d752d4703af93ea86df8f07b244e8fc851617'
---
 packages/paced/paced-async.el |  22 +-
 packages/paced/paced-tests.el | 120 +++++++----
 packages/paced/paced.el       | 473 ++++++++++++++++++++++++++++++++----------
 packages/paced/paced.info     | 278 +++++++++++++++++++++----
 packages/paced/paced.org      | 192 ++++++++++++++++-
 packages/paced/test.mk        |   2 +-
 6 files changed, 872 insertions(+), 215 deletions(-)

diff --git a/packages/paced/paced-async.el b/packages/paced/paced-async.el
index 0f239f9..97c7de1 100644
--- a/packages/paced/paced-async.el
+++ b/packages/paced/paced-async.el
@@ -7,9 +7,9 @@
 ;; Keywords: convenience, completion
 ;; Package-Requires: ((emacs "25.1") (async "1.9.1"))
 ;; URL: https://savannah.nongnu.org/projects/paced-el/
-;; Version: 1.0
+;; Version: 1.1
 ;; Created: 22 Jan 2017
-;; Modified: 08 Dec 2017
+;; Modified: 04 Feb 2018
 
 ;; This file is part of GNU Emacs.
 
@@ -38,6 +38,8 @@
 (require 'paced)
 (require 'async)
 
+(eval-when-compile (require 'subr-x))
+
 (defcustom paced-async-load-file (locate-user-emacs-file "paced-async.el")
   "File to load with user-specific population settings.
 
@@ -46,7 +48,7 @@ population commmands, or load additional files."
   :group 'paced
   :type 'file)
 
-(cl-defmethod paced-repopulate-dictionary-async ((dictionary paced-dictionary))
+(cl-defmethod paced-dictionary-repopulate-async ((dictionary paced-dictionary))
   "Repopulate DICTIONARY asynchronously.
 
 Note that DICTIONARY isn't modified directly by this process, but
@@ -92,7 +94,19 @@ Note that this will empty the dictionary's contents."
    (list (paced-read-dictionary)))
   (paced-ensure-registered key)
   (let ((dict (paced-named-dictionary key)))
-    (paced-repopulate-dictionary-async dict)))
+    (paced-dictionary-repopulate-async dict)))
+
+;;;###autoload
+(defun paced-repopulate-current-dictionary-async ()
+  "Repopulate current dictionary from its population commands, asynchronously.
+
+Population commands are stored in the field of the same name.
+
+Note that this will empty the dictionary's contents."
+  (interactive)
+  (if-let* ((dict (paced-current-dictionary)))
+      (paced-dictionary-repopulate-async dict)
+    (error "No current dictionary found")))
 
 (provide 'paced-async)
 
diff --git a/packages/paced/paced-tests.el b/packages/paced/paced-tests.el
index 700af1c..c7dc3bb 100644
--- a/packages/paced/paced-tests.el
+++ b/packages/paced/paced-tests.el
@@ -7,9 +7,9 @@
 ;; Keywords: convenience, completion
 ;; Package-Requires: ((emacs "25.1") (async "1.9.1"))
 ;; URL: https://savannah.nongnu.org/projects/paced-el/
-;; Version: 1.0
+;; Version: 1.1
 ;; Created: 22 Jan 2017
-;; Modified: 08 Dec 2017
+;; Modified: 04 Feb 2018
 
 ;; This file is part of GNU Emacs.
 
@@ -89,11 +89,11 @@
 (ert-deftest paced-enable-list-symbol ()
   "Test case for `paced-dictionary-enable-alist' being an arbitrary symbol."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((paced-test-enable-symbol . 
"test-dict-case")))
-         (new-buffer (find-file-noselect paced-first-test-file))
-         (new-dict (paced-make-dictionary "test-dict-case"
-                                          paced-test-dict-save-file
-                                          'downcase)))
+         (paced-global-dictionary-enable-alist '((paced-test-enable-symbol . 
"test-dict-case")))
+         (new-buffer (find-file-noselect paced-first-test-file)))
+    (paced-make-dictionary "test-dict-case"
+                           paced-test-dict-save-file
+                           'downcase)
     (with-current-buffer new-buffer
       (setq paced-test-enable-symbol nil)
       (should-not (paced-current-dictionary))
@@ -105,12 +105,12 @@
 (ert-deftest paced-enable-list-mode ()
   "Test case for `paced-dictionary-enable-alist' being a mode symbol."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (buffer-one (find-file-noselect paced-first-test-file))
-         (buffer-two (find-file-noselect paced-second-test-file))
-         (new-dict (paced-make-dictionary "test-dict-case"
-                                          paced-test-dict-save-file
-                                          'downcase)))
+         (buffer-two (find-file-noselect paced-second-test-file)))
+    (paced-make-dictionary "test-dict-case"
+                           paced-test-dict-save-file
+                           'downcase)
     (with-current-buffer buffer-two
       (should-not (paced-current-dictionary)))
     (kill-buffer buffer-two)
@@ -125,11 +125,11 @@
 (ert-deftest paced-enable-list-function-symbol ()
   "Test case for `paced-dictionary-enable-alist' being a function symbol."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((paced-test-function-symbol . 
"test-dict-case")))
-         (buffer-one (find-file-noselect paced-first-test-file))
-         (new-dict (paced-make-dictionary "test-dict-case"
-                                          paced-test-dict-save-file
-                                          'downcase)))
+         (paced-global-dictionary-enable-alist '((paced-test-function-symbol . 
"test-dict-case")))
+         (buffer-one (find-file-noselect paced-first-test-file)))
+    (paced-make-dictionary "test-dict-case"
+                           paced-test-dict-save-file
+                           'downcase)
     (with-current-buffer buffer-one
       (setq-local paced-test-enable-symbol nil)
       (should-not (paced-current-dictionary))
@@ -141,11 +141,11 @@
 (ert-deftest paced-enable-list-lambda-function ()
   "Test case for `paced-dictionary-enable-alist' being a lambda form."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '(((lambda nil 
paced-test-enable-symbol) . "test-dict-case")))
-         (buffer-one (find-file-noselect paced-first-test-file))
-         (new-dict (paced-make-dictionary "test-dict-case"
-                                          paced-test-dict-save-file
-                                          'downcase)))
+         (paced-global-dictionary-enable-alist '(((lambda nil 
paced-test-enable-symbol) . "test-dict-case")))
+         (buffer-one (find-file-noselect paced-first-test-file)))
+    (paced-make-dictionary "test-dict-case"
+                           paced-test-dict-save-file
+                           'downcase)
     (with-current-buffer buffer-one
       (setq-local paced-test-enable-symbol nil)
       (should-not (paced-current-dictionary))
@@ -157,12 +157,12 @@
 (ert-deftest paced-enable-list-and-form ()
   "Test case for `paced-dictionary-enable-alist' being an 'and' form."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '(((and text-mode 
paced-test-enable-symbol) . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '(((and text-mode 
paced-test-enable-symbol) . "test-dict-case")))
          (buffer-one (find-file-noselect paced-first-test-file))
-         (buffer-two (find-file-noselect paced-second-test-file))
-         (new-dict (paced-make-dictionary "test-dict-case"
-                                          paced-test-dict-save-file
-                                          'downcase)))
+         (buffer-two (find-file-noselect paced-second-test-file)))
+    (paced-make-dictionary "test-dict-case"
+                           paced-test-dict-save-file
+                           'downcase)
     (with-current-buffer buffer-two
       (setq-local paced-test-enable-symbol nil)
       (should-not (paced-current-dictionary))
@@ -180,12 +180,12 @@
 (ert-deftest paced-enable-list-or-form ()
   "Test case for `paced-dictionary-enable-alist' being an 'or' form."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '(((or text-mode 
paced-test-enable-symbol) . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '(((or text-mode 
paced-test-enable-symbol) . "test-dict-case")))
          (buffer-one (find-file-noselect paced-first-test-file))
-         (buffer-two (find-file-noselect paced-second-test-file))
-         (new-dict (paced-make-dictionary "test-dict-case"
-                                          paced-test-dict-save-file
-                                          'downcase)))
+         (buffer-two (find-file-noselect paced-second-test-file)))
+    (paced-make-dictionary "test-dict-case"
+                           paced-test-dict-save-file
+                           'downcase)
     (with-current-buffer buffer-two
       (setq-local paced-test-enable-symbol nil)
       (should-not (paced-current-dictionary))
@@ -205,7 +205,7 @@
 (ert-deftest paced-populate-file ()
   "Test case for single file populator."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-file-population-command :file paced-first-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
                                            paced-test-dict-save-file
@@ -224,7 +224,7 @@
 (ert-deftest paced-populate-buffer ()
   "Test case for single buffer populator."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (buffer "first.txt")
          (buffer-one (find-file-noselect paced-first-test-file))
          (cmd (paced-buffer-population-command :buffer buffer))
@@ -246,7 +246,7 @@
 (ert-deftest paced-populate-file-function ()
   "Test case for file-function populator."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (pre-func (lambda () (insert (buffer-string)) t))
          (cmd (paced-file-function-population-command :file 
paced-first-test-file
                                                       :setup-func pre-func))
@@ -267,7 +267,7 @@
 (ert-deftest paced-populate-directory-regexp ()
   "Test case for directory-regexp populator."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-directory-regexp-population-command :directory 
paced-test-dir
                                                          :regexp ".*\\.txt"
                                                          :recursive t))
@@ -288,12 +288,12 @@
 (ert-deftest paced-populate-file-list ()
   "Test case for file-list populator."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (file-list (lambda () `(,paced-first-test-file)))
          (cmd (paced-file-list-population-command :generator file-list))
          (test-dict (paced-make-dictionary "test-dict-case"
-                                           paced-test-dict-save-file
-                                           'downcase)))
+                           paced-test-dict-save-file
+                           'downcase)))
     (should (paced-dictionary-p test-dict))
     (oset test-dict population-commands (list cmd))
     (paced-dictionary-repopulate test-dict)
@@ -307,7 +307,7 @@
 
 (ert-deftest paced-multiple-population-commands ()
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd1 (paced-file-population-command :file paced-first-test-file))
          (cmd2 (paced-file-population-command :file paced-third-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
@@ -329,7 +329,7 @@
 
 (ert-deftest paced-populator-settings ()
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (exclude-command (lambda nil (nth 8 (syntax-ppss)))) ;; exclude 
comments
          (cmd1 (paced-file-population-command :file paced-first-test-file))
          (cmd2 (paced-file-population-command :file paced-second-test-file
@@ -351,7 +351,7 @@
 (ert-deftest paced-populate-sort-order ()
   "Test case for sorting after population."
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-file-population-command :file paced-first-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
                                            paced-test-dict-save-file
@@ -395,7 +395,7 @@
 
 (ert-deftest paced-completions-try-completion ()
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-file-population-command :file paced-first-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
                                            paced-test-dict-save-file
@@ -408,7 +408,7 @@
 
 (ert-deftest paced-completions-try-completion-mixed-case ()
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-file-population-command :file paced-first-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
                                            paced-test-dict-save-file
@@ -421,7 +421,7 @@
 
 (ert-deftest paced-completions-all-completions ()
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-file-population-command :file paced-first-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
                                            paced-test-dict-save-file
@@ -440,7 +440,7 @@
 
 (ert-deftest paced-completions-test-completion ()
   (let* ((paced--registered-dictionaries paced-test-default-registered-map)
-         (paced-global-dict-enable-alist '((text-mode . "test-dict-case")))
+         (paced-global-dictionary-enable-alist '((text-mode . 
"test-dict-case")))
          (cmd (paced-file-population-command :file paced-first-test-file))
          (test-dict (paced-make-dictionary "test-dict-case"
                                            paced-test-dict-save-file
@@ -463,6 +463,34 @@
     (should (equal (map-elt merged-2 'a) '("def")))
     (should (equal (map-elt merged-2 'c) '("ghi")))))
 
+(ert-deftest paced-beginning-end-of-thing-at-point ()
+  "Test exclusion and `paced-point-in-thing-at-point-for-exclusion'."
+  (let* ((paced-exclude-function (lambda () (looking-at-p "one")))
+         (buffer-one (find-file-noselect paced-first-test-file)))
+    (with-current-buffer buffer-one
+      (goto-char (point-min))
+      (should (equal (paced-thing-at-point) "one"))
+      (let ((paced-point-in-thing-at-point-for-exclusion 'beginning))
+        (should (paced-excluded-p)))
+      (paced-forward-thing)
+      (should (equal (paced-thing-at-point) "one"))
+      (let ((paced-point-in-thing-at-point-for-exclusion 'end))
+        (should-not (paced-excluded-p))))))
+
+(ert-deftest paced-character-limit ()
+  "Test character limit."
+  (let* ((paced-exclude-function (lambda () nil))
+         (buffer-one (find-file-noselect paced-first-test-file)))
+    (with-current-buffer buffer-one
+      (goto-char (point-min))
+      (should (equal (paced-thing-at-point) "one"))
+      (let* ((paced-character-limit 0))
+        (should-not (paced-excluded-p)))
+      (let* ((paced-character-limit 1))
+        (should (paced-excluded-p)))
+      (let* ((paced-character-limit 3))
+        (should-not (paced-excluded-p))))))
+
 (provide 'paced-tests)
 
 ;;; paced-tests.el ends here
diff --git a/packages/paced/paced.el b/packages/paced/paced.el
index 6309cdb..79fe483 100644
--- a/packages/paced/paced.el
+++ b/packages/paced/paced.el
@@ -7,9 +7,9 @@
 ;; Keywords: convenience, completion
 ;; Package-Requires: ((emacs "25.1") (async "1.9.1"))
 ;; URL: https://savannah.nongnu.org/projects/paced-el/
-;; Version: 1.0.1
+;; Version: 1.1
 ;; Created: 22 Jan 2017
-;; Modified: 08 Dec 2017
+;; Modified: 04 Feb 2018
 
 ;; This file is part of GNU Emacs.
 
@@ -118,6 +118,27 @@ allowing all files."
   :group 'paced
   :type 'boolean)
 
+(defcustom paced-point-in-thing-at-point-for-exclusion 'beginning
+  "Symbol to indicate from where exclusion should occur.
+
+If 'beginning, exclusion is checked at the beginning of the thing
+at point.  If 'end, exclusion is checked at the end of the thing
+at point.
+
+See `paced-excluded-p' and `paced-exclude-function' for more
+information on exclusion."
+  :group 'paced
+  :type 'symbol
+  :options '(beginning end))
+
+(defcustom paced-character-limit 0
+  "Character limit for including things in paced.
+
+If set to 0, impose no limit.  Otherwise, include words whose
+length is less than or equal to the value."
+  :group 'paced
+  :type 'number)
+
 
 
 (defun paced--default-dictionary-sort-func (usage-hash)
@@ -249,10 +270,18 @@ If none exists, return nil."
   "Return non-nil if a dictionary with name KEY has been registered."
   (map-contains-key paced--registered-dictionaries key))
 
-(defsubst paced-ensure-registered (key)
-  "Throw an error if a dictionary with name KEY has not been registered."
-  (unless (paced-dictionary-key-registered-p key)
-    (error "No paced dictionary called '%s' has been registered" key)))
+(defvar paced-throw-error-on-no-registered t
+  "Whether to throw an error when a named dictionary can't be found.")
+
+(defmacro paced-operate-on-named-dictionary (name &rest form)
+  "Run FORM on the dictionary with name NAME, bound to `dict'.
+
+If no dictionary named NAME exists, throw an error."
+  (declare (indent 1))
+  `(if-let* ((dict (paced-named-dictionary ,name)))
+       (progn ,@form)
+     (when paced-throw-error-on-no-registered
+       (error "No paced dictionary called '%s' has been registered" ,name))))
 
 (cl-defmethod paced-dictionary-register ((dict paced-dictionary))
   "Registered dictionary DICT."
@@ -302,7 +331,9 @@ customization interface."
   "Return the name of dictionary OBJ."
   (oref obj object-name))
 
-(defcustom paced-global-dict-enable-alist nil
+;;; Current Dictionary
+
+(defcustom paced-global-dictionary-enable-alist nil
   "List that determines which dictionaries should be active.
 
 Each entry has the form (CONDITION . DICT-KEY), where CONDITION
@@ -334,28 +365,40 @@ enabled unless `paced-mode' is active."
   :group 'paced
   :type '(alist :key-type sexp :value-type string))
 
-(defvar-local paced-local-dict-enable-alist nil
+(define-obsolete-variable-alias 'paced-global-dict-enable-alist
+  'paced-global-dictionary-enable-alist
+  "1.1")
+
+(defvar-local paced-local-dictionary-enable-alist nil
   "Local enable list.
 
 Has the same form as and takes priority over
-`paced-global-dict-enable-alist'.")
+`paced-global-dictionary-enable-alist'.")
 
-(defun paced-dict-enable-list ()
+(define-obsolete-variable-alias 'paced-local-dict-enable-alist
+  'paced-local-dictionary-enable-alist
+  "1.1")
+
+(defun paced-dictionary-enable-list ()
   "Return the combination of the local and global enable-alists.
 
-See `paced-local-dict-enable-alist' and
-`paced-global-dict-enable-alist' for more information."
-  (append paced-local-dict-enable-alist
-          paced-global-dict-enable-alist))
+See `paced-local-dictionary-enable-alist' and
+`paced-global-dictionary-enable-alist' for more information."
+  (append paced-local-dictionary-enable-alist
+          paced-global-dictionary-enable-alist))
+
+(define-obsolete-function-alias 'paced-dict-enable-list
+  'paced-dictionary-enable-list
+  "1.1")
 
 (defun paced-mode-symbol-p (sym)
   "Return non-nil if SYM is a mode symbol."
   (string-match-p (rx "-mode" string-end) (symbol-name sym)))
 
-(defun paced-test-dict-enable-condition (condition)
+(defun paced-test-dictionary-enable-condition (condition)
   "Determines if CONDITION passes in the current buffer.
 
-See `paced-global-dict-enable-alist' for an explanation."
+See `paced-global-dictionary-enable-alist' for an explanation."
   (pcase condition
     ((and (pred symbolp)
           (pred paced-mode-symbol-p))
@@ -369,9 +412,9 @@ See `paced-global-dict-enable-alist' for an explanation."
     ((pred functionp)
      (funcall condition))
     (`(or . ,rest)
-     (seq-some 'paced-test-dict-enable-condition rest))
+     (seq-some 'paced-test-dictionary-enable-condition rest))
     (`(and . ,rest)
-     (seq-every-p 'paced-test-dict-enable-condition rest))))
+     (seq-every-p 'paced-test-dictionary-enable-condition rest))))
 
 (defun paced-current-dictionary ()
   "Determine the current dictionary.
@@ -379,18 +422,39 @@ See `paced-global-dict-enable-alist' for an explanation."
 Returns nil if no dictionary should be enabled.
 
 If a dictionary is found in the list that doesn't exist, it will
-be skipped."
-  (let ((conditions (paced-dict-enable-list))
+be skipped.
+
+See `paced-global-dictionary-enable-alist' or
+`paced-local-dictionary-enable-alist' for how to set the current
+dictionary conditions."
+  (let ((conditions (paced-dictionary-enable-list))
         (dictionary))
     (while (and conditions
                 (not dictionary))
       (pcase-let* ((`(,condition . ,dict) (pop conditions)))
         (when (and (paced-dictionary-key-registered-p dict)
-                   (paced-test-dict-enable-condition condition))
+                   (paced-test-dictionary-enable-condition condition))
           (setq dictionary dict))))
     (when dictionary
       (paced-named-dictionary dictionary))))
 
+(defvar paced-throw-error-on-no-current t
+  "Whether to throw an error when no current dictionary can be
+found.")
+
+(defmacro paced-operate-on-current-dictionary (&rest form)
+  "Run FORM with the current dictionary bound to `dict'.
+
+If no dictionary can be found for the buffer, throw an error.  To
+suppress the error, set `paced-throw-error-on-no-current' to
+nil."
+  `(if-let* ((dict (paced-current-dictionary)))
+       (progn ,@form)
+     (when paced-throw-error-on-no-current
+       (user-error "No dictionary found for current buffer"))))
+
+;;; Saving and Loading
+
 (cl-defmethod paced-dictionary-save ((dict paced-dictionary) &optional force)
   "Save dictionary DICT according to its filename.
 
@@ -407,10 +471,18 @@ If FORCE is non-nil (given with a prefix arg), forcibly 
save the
 dictionary if found."
   (declare (interactive-only paced-dictionary-save))
   (interactive (list (paced-read-dictionary) current-prefix-arg))
-  (paced-ensure-registered key)
-  (let ((dict (paced-named-dictionary key)))
+  (paced-operate-on-named-dictionary key
     (paced-dictionary-save dict force)))
 
+(defun paced-save-current-dictionary (force)
+  "Save the dictionary for the current buffer.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
+  (interactive "P")
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-save dict force)))
+
 (defun paced-load-dictionary-from-file (file)
   "Load dictionary from FILE."
   (interactive
@@ -449,18 +521,65 @@ dictionary if found."
   (paced-dictionary-save dict t))
 
 
+;;; Handling of Thing at Point
 
-(defvar-local paced--current-source nil
-  "The source from which a dictionary is being populated.
+(defun paced-bounds-of-thing-at-point ()
+  "Get the bounds of the thing at point."
+  (bounds-of-thing-at-point paced-thing-at-point-constituent))
 
-This is used internally to inform the user of the current source,
-since population mostly uses temporary buffers.")
+(defun paced-thing-at-point ()
+  "Return the current thing at point.
+
+The thing is determined by `paced-thing-at-point-constituent'.
+
+Text properties are excluded."
+  (when-let* ((bounds (paced-bounds-of-thing-at-point)))
+    (buffer-substring-no-properties
+     (car bounds) (cdr bounds))))
+
+(defun paced-forward-thing (&optional number)
+  "Move forward NUMBER things.
+
+Things is based on `paced-thing-at-point-constituent'."
+  (interactive "p")
+  (forward-thing paced-thing-at-point-constituent number))
+
+(defun paced-goto-beginning-of-thing-at-point ()
+  "Move to the start of the current thing at point.
+
+Thing is based on `paced-thing-at-point-constituent'."
+  (goto-char (car (paced-bounds-of-thing-at-point))))
+
+(defun paced-goto-end-of-thing-at-point ()
+  "Move to the end of the current thing at point.
+
+Thing is based on `paced-thing-at-point-constituent'."
+  (goto-char (cdr (paced-bounds-of-thing-at-point))))
+
+;;; Character Limits
+
+(defun paced-length-of-thing-at-point ()
+  "Return the length, in characters, of the current thing at point."
+  (length (paced-thing-at-point)))
+
+(defun paced-thing-meets-limit-p ()
+  "Return non-nil if the current thing at point meets the limit requirement.
+
+The limit requirement is set with `paced-character-limit'."
+  (or (eq paced-character-limit 0)
+      (<= (paced-length-of-thing-at-point) paced-character-limit)))
+
+;;; Exclusion
 
 (defvar-local paced-exclude-function (lambda () nil)
   "Local predicate to determine if thing at point should be excluded.
 
 This should be a function of no arguments that returns non-nil if
-the current thing-at-point should be excluded from paced dictionaries.
+the current thing-at-point should be excluded from paced
+dictionaries.  Exclusion is checked from the start or the end of
+the current thing, depending on `paced-point-in-thing-at-point-for-exclusion'.
+Point returns to its original position after the function is
+called.
 
 By default, this allows everything.
 
@@ -475,29 +594,25 @@ If POS is not specified, defaults to `point'."
 (defun paced-excluded-p ()
   "Return non-nil to exclude current thing at point.
 
-See `paced-exclude-function' for more."
-  (funcall paced-exclude-function))
+See `paced-exclude-function' for more.
 
-(defun paced-bounds-of-thing-at-point ()
-  "Get the bounds of the thing at point."
-  (bounds-of-thing-at-point paced-thing-at-point-constituent))
+Exclusion can be performed from either the beginning or end of
+the thing at point.  See
+`paced-point-in-thing-at-point-for-exclusion' for how to set
+this.
 
-(defun paced-thing-at-point ()
-  "Return the current thing at point.
-
-The thing is determined by `paced-thing-at-point-constituent'.
+This also handles character limits set by
+`paced-character-limit'."
+  (or (not (paced-thing-meets-limit-p))
+      (save-excursion
+        (pcase paced-point-in-thing-at-point-for-exclusion
+          (`beginning
+           (paced-goto-beginning-of-thing-at-point))
+          (`end
+           (paced-goto-end-of-thing-at-point)))
+        (funcall paced-exclude-function))))
 
-Text properties are excluded."
-  (when-let* ((bounds (paced-bounds-of-thing-at-point)))
-    (buffer-substring-no-properties
-     (car bounds) (cdr bounds))))
-
-(defun paced-forward-thing (&optional number)
-  "Move forward NUMBER things.
-
-Things is based on `paced-thing-at-point-constituent'."
-  (interactive "p")
-  (forward-thing paced-thing-at-point-constituent number))
+;;; Case Handling
 
 (defun paced-mixed-case-word-p (word)
   "Return non-nil if WORD is mixed-case.
@@ -540,6 +655,14 @@ This is a separate function only for testing; use
   "Return WORD, modified based on DICT's case handling."
   (paced--handle-word-case (oref dict case-handling) word))
 
+;;; Population
+
+(defvar-local paced--current-source nil
+  "The source from which a dictionary is being populated.
+
+This is used internally to inform the user of the current source,
+since population mostly uses temporary buffers.")
+
 (cl-defmethod paced-dictionary-add-word ((dict paced-dictionary) word)
   "Add WORD to paced dictionary DICT."
   (let ((new-word (paced-dictionary-process-word dict word)))
@@ -547,11 +670,13 @@ This is a separate function only for testing; use
     (cl-incf (map-elt (oref dict usage-hash) new-word 0))
     (oset dict updated t)))
 
-(defsubst paced-add-word-to-current-dict (word)
-  "Add WORD to the current paced dictionary."
-  (if-let* ((dict (paced-current-dictionary)))
-      (paced-dictionary-add-word dict word)
-    (error "No current dictionary found")))
+(defun paced-add-word-to-current-dictionary (word)
+  "Add WORD to the current dictionary.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-add-word dict word)))
 
 (cl-defmethod paced-dictionary-populate-from-buffer ((dict paced-dictionary) 
&optional buffer)
   "Repopulate DICT from BUFFER.
@@ -574,41 +699,76 @@ If BUFFER is nil, use the current one."
             (paced-dictionary-add-word dict (paced-thing-at-point))))
         (progress-reporter-done reporter)))))
 
-(defun paced-populate-dictionary-from-region (dict start end)
-  "Populate DICT from the region in the current buffer between START and END.
+(defun paced-populate-current-dictionary-from-buffer (&optional buffer)
+  "Populate the current dictionary from BUFFER.
 
-Note that this doesn't add the current buffer to DICT's
-population commands, so if DICT is later repopulated using
+This means add a usage of each included thing in BUFFER.
+
+BUFFER is either the name of a buffer, or the buffer itself.  If
+not given, use the current buffer.
+
+In order to only populate the dictionary from a region,
+`paced-populate-current-dictionary-from-region'.
+
+Note that this doesn't add the buffer to the dictionary's
+population commands, so if it is later repopulated using
 `paced-dictionary-repopulate' or
 `paced-repopulate-named-dictionary', anything added with this
-command will be lost."
-  (save-restriction
-    (narrow-to-region start end)
-    (paced-dictionary-populate-from-buffer dict)))
+command will be lost.
+
+In order to make changes permanent, use
+`paced-add-buffer-file-to-dictionary'.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
+  (interactive "b")
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-populate-from-buffer dict buffer)))
+
+(define-obsolete-function-alias 'paced-populate-buffer-dictionary
+  'paced-populate-current-dictionary-from-buffer
+  "1.1")
+
+(defun paced-populate-named-dictionary-from-buffer (name &optional buffer)
+  "Populate the dictionary named NAME from BUFFER.
 
-(defun paced-populate-buffer-dictionary (&optional buffer)
-  "Populate BUFFER's current dictionary with BUFFER.
+This means add a usage of each included thing in BUFFER.
 
-This means add a usage of each included thing in buffer.
+BUFFER is either the name of a buffer, or the buffer itself.  If
+not given, use the current buffer.
 
-If called interactively, the current buffer is used.  In order to
-only populate the dictionary from a region,
-`paced-populate-from-region'.
+In order to only populate the dictionary from a region,
+`paced-populate-current-dictionary-from-region'.
 
-Note that this doesn't add BUFFER to the dictionary's population
-commands, so if it is later repopulated using
+Note that this doesn't add the buffer to the dictionary's
+population commands, so if it is later repopulated using
 `paced-dictionary-repopulate' or
 `paced-repopulate-named-dictionary', anything added with this
 command will be lost.
 
 In order to make changes permanent, use
-`paced-add-buffer-file-to-dictionary'."
-  (interactive)
-  (if-let* ((dict (paced-current-dictionary)))
-      (paced-dictionary-populate-from-buffer dict buffer)
-    (user-error "No dictionary found")))
+`paced-add-buffer-file-to-dictionary'.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
+  (interactive
+   (list (paced-read-dictionary) (read-buffer "Buffer: " nil t)))
+  (paced-operate-on-named-dictionary name
+    (paced-dictionary-populate-from-buffer dict buffer)))
+
+(cl-defmethod paced-dictionary-populate-from-region ((dict paced-dictionary) 
start end)
+  "Populate DICT from the region in the current buffer between START and END.
+
+Note that this doesn't add the current buffer to DICT's
+population commands, so if DICT is later repopulated using
+`paced-dictionary-repopulate' or
+`paced-repopulate-named-dictionary', anything added with this
+command will be lost."
+  (save-restriction
+    (narrow-to-region start end)
+    (paced-dictionary-populate-from-buffer dict)))
 
-(defun paced-populate-from-region (start end)
+(defun paced-populate-current-dictionary-from-region (start end)
   "Populate the current dictionary from the region START to END.
 
 Note that this doesn't add the current buffer to the dictionary's
@@ -617,11 +777,14 @@ population commands, so if it is later repopulated using
 `paced-repopulate-named-dictionary', anything added with this
 command will be lost."
   (interactive "r")
-  (if-let* ((dict (paced-current-dictionary)))
-      (paced-populate-dictionary-from-region dict start end)
-    (user-error "No dictionary found")))
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-populate-from-region dict start end)))
 
-(defun paced-add-current-thing-to-dict ()
+(define-obsolete-function-alias 'paced-populate-from-region
+  'paced-populate-current-dictionary-from-region
+  "1.1")
+
+(defun paced-add-current-thing-to-dictionary ()
   "Add the current thing at point to the current dictionary.
 
 No check is done to determine if the current thing should be
@@ -633,7 +796,10 @@ population commands, so if it is later repopulated using
 `paced-repopulate-named-dictionary', anything added with this
 command will be lost."
   (interactive)
-  (paced-add-word-to-current-dict (paced-thing-at-point)))
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-add-word dict (paced-thing-at-point))))
+
+;;; Dictionary Reset
 
 (cl-defmethod paced-dictionary-reset ((dict paced-dictionary))
   "Reset the usage-hash of paced-dictionary DICT."
@@ -641,12 +807,23 @@ command will be lost."
 
 (defun paced-reset-named-dictionary (key)
   "Reset the paced dictionary with key KEY."
+  (declare (interactive-only paced-dictionary-reset))
   (interactive
    (list (paced-read-dictionary)))
-  (paced-ensure-registered key)
-  (let ((dict (paced-named-dictionary key)))
+  (paced-operate-on-named-dictionary key
     (paced-dictionary-reset dict)))
 
+(defun paced-reset-current-dictionary ()
+  "Reset the current dictionary.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
+  (interactive)
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-reset dict)))
+
+;;; Dictionary Sorting
+
 (cl-defmethod paced-dictionary-sort ((dict paced-dictionary))
   "Sort the words in dictionary DICT by usage."
   (oset dict usage-hash
@@ -655,12 +832,22 @@ command will be lost."
 
 (defun paced-sort-named-dictionary (key)
   "Sort the paced dictionary with key KEY."
+  (declare (interactive-only paced-dictionary-sort))
   (interactive (list (paced-read-dictionary)))
-  (paced-ensure-registered key)
-  (let ((dict (paced-named-dictionary key)))
+  (paced-operate-on-named-dictionary key
     (paced-dictionary-sort dict)))
 
+(defun paced-sort-current-dictionary ()
+  "Sort the current paced dictionary.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
+  (interactive)
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-sort dict)))
+
 
+;;; The Minor Mode
 
 (define-minor-mode paced-mode
   "Toggle paced mode.
@@ -677,10 +864,7 @@ This adds `paced-completion-at-point' to
 (define-globalized-minor-mode global-paced-mode paced-mode paced-mode
   :group 'paced)
 
-
-                                        ; ;;;;;;;;;;;;;;;; ;
-                                        ; ;; Completion ;; ;
-                                        ; ;;;;;;;;;;;;;;;; ;
+;;; Completion
 
 (cl-defmethod paced-dictionary-fix-completion-case ((dict paced-dictionary) 
prefix completions)
   "Account for case differences in the prefix by prepending PREFIX to 
COMPLETIONS.
@@ -755,6 +939,8 @@ the prefix before completions are returned."
   (let* ((completion-ignore-case paced-completion-ignore-case))
     (pcase action
       ((or `nil `t `lambda)
+       ;; Intentionally don't throw an error here, so as not to disrupt
+       ;; completion.
        (when-let* ((dict (paced-current-dictionary)))
          (paced-dictionary-completions dict string action pred)))
       (`(boundaries . _) nil)
@@ -784,8 +970,10 @@ This should only be called from 
`paced-completion-at-point'."
      ;; Might not be the entire completion, so don't add it.
      )
     (finished
+     ;; We know there's a current dictionary, since this should only be called
+     ;; from `paced-completion-at-point'.
      (when paced-auto-update-p
-       (paced-add-word-to-current-dict word)))))
+       (paced-add-word-to-current-dictionary word)))))
 
 (defun paced-completion-at-point ()
   "Function for `completion-at-point-functions' to get the paced completions."
@@ -797,9 +985,7 @@ This should only be called from 
`paced-completion-at-point'."
             :exclusive 'no))))
 
 
-                                        ; ;;;;;;;;;;;;;;;;;; ;
-                                        ; ;; Repopulation ;; ;
-                                        ; ;;;;;;;;;;;;;;;;;; ;
+;;; Repopulation
 
 (defun paced--insert-file-contents (file)
   "Insert the contents of FILE into the current buffer.
@@ -1011,19 +1197,30 @@ repopulating it.  If `paced-populate-warn-about-reset' 
is
 non-nil, confirmation will be requested before continuing."
   (interactive
    (list (paced-read-dictionary)))
-  (paced-ensure-registered key)
-  (let ((dict (paced-named-dictionary key)))
+  (paced-operate-on-named-dictionary key
     (when (or (not paced-populate-warn-about-reset)
               (y-or-n-p "Warning: Repopulating dictionary will reset it.  
Continue?"))
       (paced-dictionary-repopulate dict))))
 
+(defun paced-repopulate-current-dictionary ()
+  "Repopulate the current dictionary.
+
+Note that this will empty the dictionary's contents before
+repopulating it.  If `paced-populate-warn-about-reset' is
+non-nil, confirmation will be requested before continuing."
+  (interactive)
+  (paced-operate-on-current-dictionary
+   (when (or (not paced-populate-warn-about-reset)
+             (y-or-n-p "Warning: Repopulating dictionary will reset it.  
Continue?"))
+     (paced-dictionary-repopulate dict))))
+
 (cl-defmethod paced-dictionary-add-population-command ((dict paced-dictionary)
                                                        (cmd 
paced-population-command))
   "Add population command CMD to dictionary DICT."
   (cl-pushnew cmd (oref dict population-commands) :test 'equal))
 
 (defun paced-add-buffer-file-to-dictionary (&optional buffer)
-  "Populate the dictionary of BUFFER with BUFFER.
+  "Populate the current dictionary of BUFFER with BUFFER.
 
 The file corresponding to BUFFER is then added to the current
 dictionary's population commands.
@@ -1035,33 +1232,91 @@ must be set with `paced-edit-named-dictionary' or
   (with-current-buffer (or buffer (current-buffer))
     (unless (buffer-file-name)
       (user-error "paced-add-buffer-file-to-dictionary called inside a 
non-file buffer."))
-    (if-let* ((dict      (paced-current-dictionary))
-              (file-name (buffer-file-name))
-              (cmd (paced-file-population-command :file file-name)))
-        (progn
-          (paced-dictionary-populate-from-buffer dict buffer)
-          (paced-dictionary-add-population-command dict cmd))
-      (user-error "No dictionary found for current buffer"))))
+    (paced-operate-on-current-dictionary
+     (let* ((file-name (buffer-file-name))
+            (cmd (paced-file-population-command :file file-name)))
+       (paced-dictionary-populate-from-buffer dict buffer)
+       (paced-dictionary-add-population-command dict cmd)))))
 
 
 
+;;; Edit a Dictionary
+
 (cl-defmethod paced-dictionary-edit ((dict paced-dictionary))
   "Edit paced-dictionary DICT."
   (customize-object dict))
 
 (defun paced-edit-named-dictionary (name)
   "Edit the paced-dictionary named NAME."
+  (declare (interactive-only paced-dictionary-edit))
   (interactive (list (paced-read-dictionary)))
-  (if-let* ((dict (paced-named-dictionary name)))
-      (paced-dictionary-edit dict)
-    (error "No paced dictionary called '%s' has been registered" name)))
+  (paced-operate-on-named-dictionary name
+    (paced-dictionary-edit dict)))
 
 (defun paced-edit-current-dictionary ()
-  "Edit the current paced dictionary."
+  "Edit the current paced dictionary.
+
+For how the current dictionary is determined, see
+`paced-current-dictionary'."
   (interactive)
-  (if-let* ((dict (paced-current-dictionary)))
-      (paced-dictionary-edit dict)
-    (user-error "No dictionary found for current buffer")))
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-edit dict)))
+
+
+
+;;; Print a Dictionary in a Dedicated Buffer
+
+(defvar-local paced-tabulated-list-dictionary nil
+  "Dictionary printed in a tabulated list buffer.")
+
+(cl-defmethod paced-dictionary-length-of-longest-word ((dict paced-dictionary))
+  "Return the length of the longest word in DICT."
+  (seq-max
+   (map-apply
+    (lambda (key _value)
+      (length key))
+    (oref dict usage-hash))))
+
+(cl-defmethod paced-dictionary-tabulated-list-entries ((dict paced-dictionary))
+  "Create a value for `tabulated-list-entries' from DICT."
+  (map-apply
+   (lambda (key value)
+     (list key (vector key (number-to-string value))))
+   (oref dict usage-hash)))
+
+(defun paced-tabulated-list-revert ()
+  "Revert a `tabulated-list-mode' buffer from its dictionary."
+  (let* ((dict paced-tabulated-list-dictionary)
+         (longest-length (paced-dictionary-length-of-longest-word dict)))
+    (setq tabulated-list-format
+          (vector `("Word" ,longest-length t . (:right-align t))
+                  `("Count" 10 t)))
+    (setq tabulated-list-entries (paced-dictionary-tabulated-list-entries 
dict))))
+
+(cl-defmethod paced-dictionary-print ((dict paced-dictionary))
+  "Print the contents of DICT in a dedicated buffer."
+  (let* ((buffer-name (format "*Paced Dictionary - %s*" (paced-dictionary-name 
dict)))
+         (buffer (get-buffer-create buffer-name)))
+    (with-current-buffer buffer
+      (tabulated-list-mode)
+      (setq paced-tabulated-list-dictionary dict)
+      (paced-tabulated-list-revert)
+      (tabulated-list-init-header)
+      (tabulated-list-print))
+    (display-buffer buffer)))
+
+(defun paced-print-current-dictionary ()
+  "Print the contents of the current dictionary in a dedicated buffer."
+  (interactive)
+  (paced-operate-on-current-dictionary
+   (paced-dictionary-print)))
+
+(defun paced-print-named-dictionary (name)
+  "Print the contents of the dictionary with name NAME."
+  (declare (interactive-only paced-dictionary-print))
+  (interactive (list (paced-read-dictionary)))
+  (paced-operate-on-named-dictionary name
+    (paced-dictionary-print name)))
 
 
 
diff --git a/packages/paced/paced.info b/packages/paced/paced.info
index 1fb5c84..7ab606e 100644
--- a/packages/paced/paced.info
+++ b/packages/paced/paced.info
@@ -41,6 +41,7 @@ Dictionaries
 * Editing a Dictionary::         How to edit your new dictionary
 * Selective Dictionaries::       Enabling certain dictionaries under certain 
conditions
 * Dictionary Files::             Loading and Saving the Dictionaries
+* Printing a Dictionary::        Seeing the contents of a dictionary
 
 Population Commands
 
@@ -53,6 +54,9 @@ Example Setups
 
 * Org Agenda Files::
 * Project Files::
+* Markdown Files::
+* Repopulating Dictionary After Saving::
+* Repopulating Dictionary After Spellchecking the Buffer::
 
 Contributing
 
@@ -63,6 +67,7 @@ Contributing
 
 Changelog
 
+* 1.1: 11.
 * 1.0.1: 101.
 * 1.0: 10.
 
@@ -191,17 +196,27 @@ Installation
 Emacs   25.1
 async   1.9.1
 
-   Right now, the only way to install paced is from source.
+   Paced may be installed from source, or from GNU ELPA.
+
+   From ELPA:
+
+     M-x package-install RET paced RET
 
    From Source:
 
      bzr branch https://bzr.savannah.gnu.org/r/paced-el paced
 
-   After that, add the following to your init file (typically .emacs):
+   After installing from source, add the following to your init file
+(typically .emacs):
 
      (add-to-list 'load-path "/full/path/to/paced/")
      (require 'paced)
 
+   However you install paced, you must also make sure dictionaries are
+loaded on startup:
+
+     (paced-load-all-dictionaries)
+
 
 File: paced.info,  Node: Basic Setup,  Prev: Installation,  Up: Introduction
 
@@ -213,8 +228,8 @@ is as follows:
 
   1. Create a new dictionary, “Default” (See *note Creating a
      Dictionary::)
-  2. Set paced-global-dict-enable-alist to ‘((t . "Default"))’ (See
-     *note Selective Dictionaries::)
+  2. Set ‘paced-global-dictionary-enable-alist’ to ‘((t . "Default"))’
+     (See *note Selective Dictionaries::)
   3. Run ‘M-x global-paced-mode’
   4. To add a file to the dictionary, use ‘M-x
      paced-add-buffer-file-to-dictionary’
@@ -234,6 +249,7 @@ Dictionaries
 * Editing a Dictionary::         How to edit your new dictionary
 * Selective Dictionaries::       Enabling certain dictionaries under certain 
conditions
 * Dictionary Files::             Loading and Saving the Dictionaries
+* Printing a Dictionary::        Seeing the contents of a dictionary
 
 
 File: paced.info,  Node: Creating a Dictionary,  Next: Editing a Dictionary,  
Up: Dictionaries
@@ -284,9 +300,11 @@ Selective Dictionaries
 Paced provides a mechanism called the “enable list”, that allows a user
 to enable certain dictionaries for completion given certain conditions.
 
-   There are two enable lists: a global and local one.  They both work
-the same, with the local one taking precedence.  Each entry in the list
-has a condition and a key.
+   There are two enable lists: a global
+(‘paced-global-dictionary-enable-alist’) and local
+(‘paced-local-dictionary-enable-alist’) one.  They both work the same,
+with the local one taking precedence.  Each entry in the list has a
+condition and a key.
 
    The conditions are one of the following:
 
@@ -317,7 +335,7 @@ active.
    The key is the dictionary name you set during dictionary creation.
 
 
-File: paced.info,  Node: Dictionary Files,  Prev: Selective Dictionaries,  Up: 
Dictionaries
+File: paced.info,  Node: Dictionary Files,  Next: Printing a Dictionary,  
Prev: Selective Dictionaries,  Up: Dictionaries
 
 Dictionary Files
 ================
@@ -348,6 +366,24 @@ setting ‘paced-repopulate-saves-dictionary’ to t.  
Population is covered
 in the next section.
 
 
+File: paced.info,  Node: Printing a Dictionary,  Prev: Dictionary Files,  Up: 
Dictionaries
+
+Printing a Dictionary
+=====================
+
+Paced allows a user to print the contents of a dictionary to a buffer.
+Uses for this might be to tweak population commands or exclude
+functions, or to simply make sure a dictionary is populating correctly.
+
+   To use this feature, run:
+
+     M-x paced-print-named-dictionary RET NAME-OF-DICTIONARY RET
+
+   Or for the current dictionary:
+
+     M-x paced-print-current-dictionary RET
+
+
 File: paced.info,  Node: Population Commands,  Next: Example Setups,  Prev: 
Dictionaries,  Up: Top
 
 Population Commands
@@ -407,7 +443,7 @@ When setting the population commands of a dictionary, one 
may also set
 certain properties.  Each property is a variable binding, bound while
 the population command runs.
 
-   Two variables are of note here:
+   A few variables are of note here:
 
 paced-exclude-function
      Function of no arguments that returns non-nil if the thing at point
@@ -415,6 +451,9 @@ paced-exclude-function
 paced-thing-at-point-constituent
      Symbol defining thing on which population works.  Typically set to
      either ’symbol or ’word.
+paced-character-limit
+     Maximum length of a thing to include it in a dictionary.  If set to
+     0 (default), no limit is imposed.
 
    For convenience, properties that are intended for all population
 commands of a given dictionary may be set in the dictionary itself.  In
@@ -498,10 +537,17 @@ A common problem is that population can take a long time. 
 Some of us
 populate dictionaries from org agenda files, which can get pretty big.
 
    To solve this, paced uses the async
-(https://github.com/jwiegley/emacs-async) package.  Setup should be
-seamless; just stick whatever code you need in
-‘~/.emacs.d/paced-async.el’, type M-x
-paced-repopulate-named-dictionary-async, and push enter.
+(https://github.com/jwiegley/emacs-async) package.  Setup is seamless;
+just stick whatever code you need in ‘~/.emacs.d/paced-async.el’, and
+use one of the two population commands:
+
+   A named dictionary:
+
+     M-x paced-repopulate-named-dictionary-async RET NAME RET
+
+   Or the current dictionary:
+
+     M-x paced-repopulate-current-dictionary-async RET
 
    A few things to note about this:
 
@@ -509,7 +555,9 @@ paced-repopulate-named-dictionary-async, and push enter.
      population
   2. Asynchronous population doesn’t change anything until after
      population is finished, so a user may continue to use their
-     dictionary while population is happening.
+     dictionary while population is happening.  This also means that
+     multiple populations may run in parallel without interfering with
+     one another.
   3. Because async runs population in a separate Emacs process, any
      custom code required for population must be in paced-async.el.
      This includes additional population command types, but doesn’t
@@ -529,6 +577,9 @@ Example Setups
 
 * Org Agenda Files::
 * Project Files::
+* Markdown Files::
+* Repopulating Dictionary After Saving::
+* Repopulating Dictionary After Spellchecking the Buffer::
 
 
 File: paced.info,  Node: Org Agenda Files,  Next: Project Files,  Up: Example 
Setups
@@ -549,7 +600,20 @@ code and drawers.
 
    Done.
 
-   Now, the exclude command, which sits inside the properties option:
+   Now, the exclude command, which sits inside the properties option.
+This can be added to ‘paced-async.el’:
+
+     (require 'org)
+
+     (defun org-at-tag-p ()
+       (let* ((p (point)))
+         ;; Ignore errors from `org-get-tags-string'.
+         (ignore-errors
+           ;; Checks the match string for a tag heading, setting match-string 
1 to the
+           ;; tags.  Also sets match-beginning and match-end.
+           (org-get-tags-string)
+           (when (match-string 1)
+             (<= (match-beginning 1) p (match-end 1))))))
 
      (defun org-paced-exclude ()
        (or
@@ -557,6 +621,8 @@ code and drawers.
         (org-between-regexps-p org-drawer-regexp ":END:") ;; Doesn't catch END
         (org-in-regexp ":END:") ;; but this does
 
+        (org-at-tag-p) ;; tags
+
         (org-at-comment-p) ;; comments
         (org-in-regexp org-any-link-re) ;; links
         (org-in-block-p '("src" "quote" "verse")) ;; blocks
@@ -574,7 +640,7 @@ customize buffer as such:
    And you’re done.  See how easy that was?
 
 
-File: paced.info,  Node: Project Files,  Prev: Org Agenda Files,  Up: Example 
Setups
+File: paced.info,  Node: Project Files,  Next: Markdown Files,  Prev: Org 
Agenda Files,  Up: Example Setups
 
 Project Files
 =============
@@ -605,6 +671,89 @@ dedicated solutions for almost everything, but it makes an 
excellent
 fallback.
 
 
+File: paced.info,  Node: Markdown Files,  Next: Repopulating Dictionary After 
Saving,  Prev: Project Files,  Up: Example Setups
+
+Markdown Files
+==============
+
+Another common request is markdown files.  In order for this to work,
+you’ll need to install ‘markdown-mode’:
+
+     M-x package-install RET markdown-mode RET
+
+   After that, add the following to your ‘paced-async.el’ file:
+
+     (require 'markdown-mode)
+
+     (defun paced-markdown-exclude-p ()
+       "Taken from `markdown-flyspell-check-word-p'."
+       ;; Exclude anything markdown mode thinks flyspell should skip.
+       (or
+        ;; Ignore code blocks
+        (markdown-code-block-at-point-p)
+        (markdown-inline-code-at-point-p)
+        ;; Ignore comments
+        (markdown-in-comment-p)
+        ;; Ignore special text
+        (let ((faces (get-text-property (point) 'face)))
+          (if (listp faces)
+              (or (memq 'markdown-reference-face faces)
+                  (memq 'markdown-markup-face faces)
+                  (memq 'markdown-plain-url-face faces)
+                  (memq 'markdown-inline-code-face faces)
+                  (memq 'markdown-url-face faces))
+            (memq faces '(markdown-reference-face
+                          markdown-markup-face
+                          markdown-plain-url-face
+                          markdown-inline-code-face
+                          markdown-url-face))))))
+
+   That excludes anything that the developers of markdown-mode felt
+should be excluded from flyspell.
+
+   Set this as your exclude function in your dictionary’s settings, then
+add each markdown file by hand.
+
+
+File: paced.info,  Node: Repopulating Dictionary After Saving,  Next: 
Repopulating Dictionary After Spellchecking the Buffer,  Prev: Markdown Files,  
Up: Example Setups
+
+Repopulating Dictionary After Saving
+====================================
+
+This is a common request, although with the power of async, it’s an easy
+one to fulfill.  This will repopulate the current buffer’s dictionary
+every time you save a file with a dictionary.  This may seem daunting,
+but the dictionary will remain usable during population, and multiple
+populations won’t interfere with one another.
+
+     ;; Repopulate the current dictionary after saving
+     (add-hook 'after-save-hook 'paced-repopulate-current-dictionary-async)
+
+   Add that to your .emacs file, and paced will take it from there.
+
+   If you decide that’s too much, do the following:
+
+     M-: (remove-hook 'after-save-hook 
'paced-repopulate-current-dictionary-async) RET
+
+
+File: paced.info,  Node: Repopulating Dictionary After Spellchecking the 
Buffer,  Prev: Repopulating Dictionary After Saving,  Up: Example Setups
+
+Repopulating Dictionary After Spellchecking the Buffer
+======================================================
+
+Another request, although much trickier to do.  This one involves using
+Emacs’s advice mechanism:
+
+     (define-advice ispell-pdict-save (:after (&optional _no-query 
_force-save) paced-populate)
+       ;; Repopulate the current dictionary after running spell check
+       (paced-repopulate-current-dictionary-async))
+
+   If you decide this isn’t for you, do the following to revert the
+changes:
+
+     M-: (advice-remove #'ispell-pdict-save 
#'ispell-pdict-save@paced-populate) RET
+
+
 File: paced.info,  Node: Contributing,  Next: Changelog,  Prev: Example 
Setups,  Up: Top
 
 Contributing
@@ -671,6 +820,19 @@ can then merge that into the main development branch.
      paced-POPULATION-COMMAND-TYPE-population-command
    • Run ’make check’ to verify that your mods don’t break anything
    • Avoid additional or altered dependencies if at all possible
+   • Dictionary commands come in threes (“the operation triad”):
+       1. paced-dictionary-OPERATION, a cl-defmethod which performs
+          OPERATION on a dictionary
+       2. paced-OPERATION-on-named-dictionary, an interactive only
+          function that prompts for a dictionary name and performs
+          OPERATION on that dictionary:
+                    (interactive (list (paced-read-dictionary)))
+                    (paced-ensure-registered name)
+                    (paced-dictionary-OPERATION (paced-named-dictionary name))
+       3. paced-OPERATION-on-current-dictionary, an interactive function
+          that performs OPERATION on the current dictionary
+                    (interactive)
+                    (paced-dictionary-OPERATION 
(paced-current-dictionary-or-die))
 
 
 File: paced.info,  Node: Documentation,  Next: Working with EDE,  Prev: 
Development,  Up: Contributing
@@ -721,11 +883,32 @@ Changelog
 
 * Menu:
 
+* 1.1: 11.
 * 1.0.1: 101.
 * 1.0: 10.
 
 
-File: paced.info,  Node: 101,  Next: 10,  Up: Changelog
+File: paced.info,  Node: 11,  Next: 101,  Up: Changelog
+
+1.1
+===
+
+   • Cleaned up the code to reflect the “operation triad”
+        • -OP, OP-on-named, OP-on-current
+        • Retained backwards compatibility by obsoleting a bunch of
+          functions, but didn’t remove any of them
+        • Also removed the use of dict- in global variables and
+          functions
+
+   • Added the ability to print the contents of a dictionary in a
+     separate buffer
+
+   • Added the option to limit the words added during population by size
+
+   • Various documentation improvements
+
+
+File: paced.info,  Node: 101,  Next: 10,  Prev: 11,  Up: Changelog
 
 1.0.1
 =====
@@ -746,34 +929,39 @@ Initial release.
 
 Tag Table:
 Node: Top228
-Node: Copying1772
-Node: Introduction2591
-Node: Similar Packages3711
-Node: pabbrev3997
-Node: predictive5140
-Node: Installation6188
-Node: Basic Setup6653
-Node: Dictionaries7256
-Node: Creating a Dictionary7660
-Node: Editing a Dictionary8700
-Node: Selective Dictionaries9176
-Node: Dictionary Files10825
-Node: Population Commands11916
-Node: Built-in Commands12852
-Node: Properties13649
-Node: Custom Commands14511
-Node: Asynchronous Population17238
-Node: Example Setups18431
-Node: Org Agenda Files18613
-Node: Project Files19869
-Node: Contributing20982
-Node: Bugs21756
-Node: Development22145
-Node: Documentation23112
-Node: Working with EDE23579
-Node: Changelog24622
-Node: 10124747
-Node: 1024944
+Node: Copying1971
+Node: Introduction2790
+Node: Similar Packages3910
+Node: pabbrev4196
+Node: predictive5339
+Node: Installation6387
+Node: Basic Setup7049
+Node: Dictionaries7664
+Node: Creating a Dictionary8137
+Node: Editing a Dictionary9177
+Node: Selective Dictionaries9653
+Node: Dictionary Files11391
+Node: Printing a Dictionary12512
+Node: Population Commands13034
+Node: Built-in Commands13970
+Node: Properties14767
+Node: Custom Commands15765
+Node: Asynchronous Population18492
+Node: Example Setups19941
+Node: Org Agenda Files20242
+Node: Project Files22002
+Node: Markdown Files23138
+Node: Repopulating Dictionary After Saving24763
+Node: Repopulating Dictionary After Spellchecking the Buffer25691
+Node: Contributing26436
+Node: Bugs27210
+Node: Development27599
+Node: Documentation29360
+Node: Working with EDE29827
+Node: Changelog30870
+Node: 1131006
+Node: 10131601
+Node: 1031809
 
 End Tag Table
 
diff --git a/packages/paced/paced.org b/packages/paced/paced.org
index c3c4596..8a6e54e 100644
--- a/packages/paced/paced.org
+++ b/packages/paced/paced.org
@@ -98,7 +98,13 @@ need it to do that.
 | Emacs |  25.1 |
 | async | 1.9.1 |
 
-Right now, the only way to install paced is from source.
+Paced may be installed from source, or from GNU ELPA.
+
+From ELPA:
+
+#+begin_example
+M-x package-install RET paced RET
+#+end_example
 
 From Source:
 
@@ -106,12 +112,20 @@ From Source:
 bzr branch https://bzr.savannah.gnu.org/r/paced-el paced
 #+end_src
 
-After that, add the following to your init file (typically .emacs):
+After installing from source, add the following to your init file (typically 
.emacs):
 
 #+BEGIN_SRC emacs-lisp
 (add-to-list 'load-path "/full/path/to/paced/")
 (require 'paced)
 #+END_SRC
+
+However you install paced, you must also make sure dictionaries are loaded on
+startup:
+
+#+begin_src emacs-lisp
+(paced-load-all-dictionaries)
+#+end_src
+
 ** Basic Setup
 :PROPERTIES:
 :DESCRIPTION: The simplest setup
@@ -121,7 +135,7 @@ Paced needn't have a lot of setup to run.  In fact, the 
simplest setup is as
 follows:
 
 1. Create a new dictionary, "Default" (See [[#dictionary_creation][Creating a 
Dictionary]])
-2. Set paced-global-dict-enable-alist to ~((t . "Default"))~ (See 
[[#selective_dictionaries][Selective Dictionaries]])
+2. Set ~paced-global-dictionary-enable-alist~ to ~((t . "Default"))~ (See 
[[#selective_dictionaries][Selective Dictionaries]])
 3. Run ~M-x global-paced-mode~
 4. To add a file to the dictionary, use ~M-x 
paced-add-buffer-file-to-dictionary~
 
@@ -176,7 +190,8 @@ documented in the edit buffer.
 Paced provides a mechanism called the "enable list", that allows a user to
 enable certain dictionaries for completion given certain conditions.
 
-There are two enable lists: a global and local one.  They both work the same,
+There are two enable lists: a global (~paced-global-dictionary-enable-alist~)
+and local (~paced-local-dictionary-enable-alist~) one.  They both work the 
same,
 with the local one taking precedence.  Each entry in the list has a condition
 and a key.
 
@@ -238,6 +253,26 @@ M-x paced-save-all-dictionaries RET
 Dictionaries may also be automatically saved whenever changed by setting
 ~paced-repopulate-saves-dictionary~ to t.  Population is covered in the next
 section.
+** Printing a Dictionary
+:PROPERTIES:
+:DESCRIPTION: Seeing the contents of a dictionary
+:END:
+
+Paced allows a user to print the contents of a dictionary to a buffer.  Uses 
for
+this might be to tweak population commands or exclude functions, or to simply
+make sure a dictionary is populating correctly.
+
+To use this feature, run:
+
+#+begin_example
+M-x paced-print-named-dictionary RET NAME-OF-DICTIONARY RET
+#+end_example
+
+Or for the current dictionary:
+
+#+begin_example
+M-x paced-print-current-dictionary RET
+#+end_example
 
 * Population Commands
 :PROPERTIES:
@@ -286,12 +321,14 @@ When setting the population commands of a dictionary, one 
may also set certain
 properties.  Each property is a variable binding, bound while the population
 command runs.
 
-Two variables are of note here:
+A few variables are of note here:
 
 - paced-exclude-function :: Function of no arguments that returns non-nil if 
the
      thing at point should be excluded from population.
 - paced-thing-at-point-constituent :: Symbol defining thing on which population
      works.  Typically set to either 'symbol or 'word.
+- paced-character-limit :: Maximum length of a thing to include it in a
+     dictionary.  If set to 0 (default), no limit is imposed.
 
 For convenience, properties that are intended for all population commands of a
 given dictionary may be set in the dictionary itself.  In the event of a
@@ -374,16 +411,29 @@ in this case.
 A common problem is that population can take a long time.  Some of us populate
 dictionaries from org agenda files, which can get pretty big.
 
-To solve this, paced uses the 
[[https://github.com/jwiegley/emacs-async][async]] package.  Setup should be 
seamless; just
-stick whatever code you need in ~~/.emacs.d/paced-async.el~, type M-x
-paced-repopulate-named-dictionary-async, and push enter.
+To solve this, paced uses the 
[[https://github.com/jwiegley/emacs-async][async]] package.  Setup is seamless; 
just stick
+whatever code you need in ~~/.emacs.d/paced-async.el~, and use one of the two
+population commands:
+
+A named dictionary:
+
+#+begin_example
+M-x paced-repopulate-named-dictionary-async RET NAME RET
+#+end_example
+
+Or the current dictionary:
+
+#+begin_example
+M-x paced-repopulate-current-dictionary-async RET
+#+end_example
 
 A few things to note about this:
 
 1. Dictionaries will be automatically saved by this method after population
 2. Asynchronous population doesn't change anything until after population is
    finished, so a user may continue to use their dictionary while population is
-   happening.
+   happening.  This also means that multiple populations may run in parallel
+   without interfering with one another.
 3. Because async runs population in a separate Emacs process, any custom code
    required for population must be in paced-async.el.  This includes additional
    population command types, but doesn't include the following variables:
@@ -411,15 +461,30 @@ The generator for file-list is easy:
 
 Done.
 
-Now, the exclude command, which sits inside the properties option:
+Now, the exclude command, which sits inside the properties option.  This can be
+added to ~paced-async.el~:
 
 #+begin_src emacs-lisp
+(require 'org)
+
+(defun org-at-tag-p ()
+  (let* ((p (point)))
+    ;; Ignore errors from `org-get-tags-string'.
+    (ignore-errors
+      ;; Checks the match string for a tag heading, setting match-string 1 to 
the
+      ;; tags.  Also sets match-beginning and match-end.
+      (org-get-tags-string)
+      (when (match-string 1)
+        (<= (match-beginning 1) p (match-end 1))))))
+
 (defun org-paced-exclude ()
   (or
    ;; Drawers
    (org-between-regexps-p org-drawer-regexp ":END:") ;; Doesn't catch END
    (org-in-regexp ":END:") ;; but this does
 
+   (org-at-tag-p) ;; tags
+
    (org-at-comment-p) ;; comments
    (org-in-regexp org-any-link-re) ;; links
    (org-in-block-p '("src" "quote" "verse")) ;; blocks
@@ -467,6 +532,86 @@ small function for excluding those:
 Use that for paced-exclude-function, and you're done.  We can't necessarily
 recommend this for any programming language, as there are dedicated solutions
 for almost everything, but it makes an excellent fallback.
+** Markdown Files
+
+Another common request is markdown files.  In order for this to work, you'll 
need to install ~markdown-mode~:
+
+#+begin_example
+M-x package-install RET markdown-mode RET
+#+end_example
+
+After that, add the following to your ~paced-async.el~ file:
+
+#+begin_src emacs-lisp
+(require 'markdown-mode)
+
+(defun paced-markdown-exclude-p ()
+  "Taken from `markdown-flyspell-check-word-p'."
+  ;; Exclude anything markdown mode thinks flyspell should skip.
+  (or
+   ;; Ignore code blocks
+   (markdown-code-block-at-point-p)
+   (markdown-inline-code-at-point-p)
+   ;; Ignore comments
+   (markdown-in-comment-p)
+   ;; Ignore special text
+   (let ((faces (get-text-property (point) 'face)))
+     (if (listp faces)
+         (or (memq 'markdown-reference-face faces)
+             (memq 'markdown-markup-face faces)
+             (memq 'markdown-plain-url-face faces)
+             (memq 'markdown-inline-code-face faces)
+             (memq 'markdown-url-face faces))
+       (memq faces '(markdown-reference-face
+                     markdown-markup-face
+                     markdown-plain-url-face
+                     markdown-inline-code-face
+                     markdown-url-face))))))
+#+end_src
+
+That excludes anything that the developers of markdown-mode felt should be
+excluded from flyspell.
+
+Set this as your exclude function in your dictionary's settings, then add each
+markdown file by hand.
+
+** Repopulating Dictionary After Saving
+
+This is a common request, although with the power of async, it's an easy one to
+fulfill.  This will repopulate the current buffer's dictionary every time you
+save a file with a dictionary.  This may seem daunting, but the dictionary will
+remain usable during population, and multiple populations won't interfere with
+one another.
+
+#+begin_src emacs-lisp
+;; Repopulate the current dictionary after saving
+(add-hook 'after-save-hook 'paced-repopulate-current-dictionary-async)
+#+end_src
+
+Add that to your .emacs file, and paced will take it from there.
+
+If you decide that's too much, do the following:
+
+#+begin_example
+M-: (remove-hook 'after-save-hook 'paced-repopulate-current-dictionary-async) 
RET
+#+end_example
+
+** Repopulating Dictionary After Spellchecking the Buffer
+
+Another request, although much trickier to do.  This one involves using Emacs's
+advice mechanism:
+
+#+begin_src emacs-lisp
+(define-advice ispell-pdict-save (:after (&optional _no-query _force-save) 
paced-populate)
+  ;; Repopulate the current dictionary after running spell check
+  (paced-repopulate-current-dictionary-async))
+#+end_src
+
+If you decide this isn't for you, do the following to revert the changes:
+
+#+begin_example
+M-: (advice-remove #'ispell-pdict-save #'ispell-pdict-save@paced-populate) RET
+#+end_example
 
 * Contributing
 :PROPERTIES:
@@ -528,6 +673,21 @@ There are a few rules to follow:
 - New population commands should be named 
paced-POPULATION-COMMAND-TYPE-population-command
 - Run 'make check' to verify that your mods don't break anything
 - Avoid additional or altered dependencies if at all possible
+- Dictionary commands come in threes ("the operation triad"):
+  1. paced-dictionary-OPERATION, a cl-defmethod which performs OPERATION on a 
dictionary
+  2. paced-OPERATION-on-named-dictionary, an interactive only function that 
prompts
+     for a dictionary name and performs OPERATION on that dictionary:
+     #+begin_src emacs-lisp
+     (interactive (list (paced-read-dictionary)))
+     (paced-ensure-registered name)
+     (paced-dictionary-OPERATION (paced-named-dictionary name))
+     #+end_src
+  3. paced-OPERATION-on-current-dictionary, an interactive function that
+     performs OPERATION on the current dictionary
+     #+begin_src emacs-lisp
+     (interactive)
+     (paced-dictionary-OPERATION (paced-current-dictionary-or-die))
+     #+end_src
 
 ** Documentation
 :PROPERTIES:
@@ -574,6 +734,18 @@ and letting one of us handle it is a good way to go.
 :PROPERTIES:
 :DESCRIPTION: List of changes by version
 :END:
+** 1.1
+- Cleaned up the code to reflect the "operation triad"
+  - -OP, OP-on-named, OP-on-current
+  - Retained backwards compatibility by obsoleting a bunch of functions, but 
didn't remove any of them
+  - Also removed the use of dict- in global variables and functions
+
+- Added the ability to print the contents of a dictionary in a separate buffer
+
+- Added the option to limit the words added during population by size
+
+- Various documentation improvements
+
 ** 1.0.1
 Bug fix release
 - Save dictionaries right after they're created
diff --git a/packages/paced/test.mk b/packages/paced/test.mk
index 392cb81..fa886a3 100644
--- a/packages/paced/test.mk
+++ b/packages/paced/test.mk
@@ -16,7 +16,7 @@
 # EDE only allows arbitrary code from an external makefile, so this is how 
we've
 # got to do testing.
 
-test:
+test: compile
        @$(EMACS) \
        $(EMACSFLAGS) \
        -L "." \

Reply via email to