3

I want the auto-capitalize minor mode to work well with org-mode.

Currently, auto-capitalize does not capitalize the first word in headings or list items (i.e., the word after '*' or '-'). For example, the following transformations are not working:

* heading      -->   * Heading
** subheading  -->   ** Subheading
 - item        -->    - Item

Any ideas on how to fix auto-capitalize to do this?

I have tried changing the sentence-end regexp (which is used by auto-capitalize to decide what to capitalize), but it didn't work out. I added the following without success: \\(^\\*+ \\|^ +- \\)

Many thanks!

Constantine
  • 9,072
  • 1
  • 34
  • 49
scaramouche
  • 1,772
  • 10
  • 24

1 Answers1

2

First of all, sentence-end should match everything up to the fist word of the next sentence.

The following sets a buffer-local binding of sentence-end in org-mode buffers, which seems to work for me.

(defun org-auto-capitalize-headings-and-lists ()
  "Create a buffer-local binding of sentence-end to auto-capitalize
section headings and list items."
  (make-local-variable 'sentence-end)
  (setq sentence-end (concat (rx (or
                                  ;; headings
                                  (seq line-start (1+ "*") (1+ space))
                                  ;; list and checklist items
                                  (seq line-start (0+ space) "-" (1+ space) (? (or "[ ]" "[X]") (1+ space)))))
                             "\\|" (sentence-end))))

(add-hook 'org-mode-hook #'org-auto-capitalize-headings-and-lists)

But this is not enough; auto-capitalize checks if this-command is "equivalent" to self-insert-command, or if it is newline or newline-and-indent. Org-mode rebinds RET to org-return, so auto-capitalize does not detect that it should try to capitalize after the user hits RET.

I think this is a bug in auto-capitalize; below is a fixed version of auto-capitalize which does work with org-mode.

(defun auto-capitalize (beg end length)
  "If `auto-capitalize' mode is on, then capitalize the previous word.
The previous word is capitalized (or upcased) if it is a member of the
`auto-capitalize-words' list; or if it begins a paragraph or sentence.

Capitalization occurs only if the current command was invoked via a
self-inserting non-word character (e.g. whitespace or punctuation)\; but
if the `auto-capitalize-yank' option is set, then the first word of
yanked sentences will be capitalized as well.

Capitalization can be disabled in specific contexts via the
`auto-capitalize-predicate' variable.

This should be installed as an `after-change-function'."
  (if (and auto-capitalize
           (or (null auto-capitalize-predicate)
               (funcall auto-capitalize-predicate)))
      (cond ((or (and (or (eq this-command 'self-insert-command)
                          ;; LaTeX mode binds "." to TeX-insert-punctuation,
                          ;; and "\"" to TeX-insert-quote:
                          (let ((key (this-command-keys)))
                            ;; XEmacs `lookup-key' signals "unable to bind
                            ;; this type of event" for commands invoked via
                            ;; the mouse:
                            (and (if (and (vectorp key)
                                          (> (length key) 0)
                                          (fboundp 'misc-user-event-p)
                                          (misc-user-event-p (aref key 0)))
                                     nil
                                   (memq (lookup-key global-map key t) ;new code
                                          '(self-insert-command newline newline-and-indent))) ;new code
                                 ;; single character insertion?
                                 (= length 0)
                                 (= (- end beg) 1))))
                      (let ((self-insert-char
                             (cond ((featurep 'xemacs) ; XEmacs
                                    (event-to-character last-command-event
                                                        nil nil t))
                                   (t last-command-event)))) ; GNU Emacs
                        (not (equal (char-syntax self-insert-char) ?w))))
                 (eq this-command 'newline)
                 (eq this-command 'newline-and-indent))
             ;; self-inserting, non-word character
             (if (and (> beg (point-min))
                      (equal (char-syntax (char-after (1- beg))) ?w))
                 ;; preceded by a word character
                 (save-excursion
                   (forward-word -1)
                   (save-match-data
                     (let* ((word-start (point))
                            (text-start
                             (progn
                               (while (or (minusp (skip-chars-backward "\""))
                                          (minusp (skip-syntax-backward "\"(")))
                                 t)
                               (point)))
                            lowercase-word)
                       (cond ((and auto-capitalize-words
                                   (let ((case-fold-search nil))
                                     (goto-char word-start)
                                     (looking-at
                                      (concat "\\("
                                              (mapconcat 'downcase
                                                         auto-capitalize-words
                                                         "\\|")
                                              "\\)\\>"))))
                              ;; user-specified capitalization
                              (if (not (member (setq lowercase-word
                                                     (buffer-substring ; -no-properties?
                                                      (match-beginning 1)
                                                      (match-end 1)))
                                               auto-capitalize-words))
                                  ;; not preserving lower case
                                  (progn ; capitalize!
                                    (undo-boundary)
                                    (replace-match (find lowercase-word
                                                         auto-capitalize-words
                                                         :key 'downcase
                                                         :test 'string-equal)
                                                   t t))))
                             ((and (or (equal text-start (point-min)) ; (bobp)
                                       (progn ; beginning of paragraph?
                                         (goto-char text-start)
                                         (and (= (current-column) left-margin)
                                              (zerop (forward-line -1))
                                              (looking-at paragraph-separate)))
                                       (progn ; beginning of paragraph?
                                         (goto-char text-start)
                                         (and (= (current-column) left-margin)
                                              (re-search-backward paragraph-start
                                                                  nil t)
                                              (= (match-end 0) text-start)
                                              (= (current-column) left-margin)))
                                       (progn ; beginning of sentence?
                                         (goto-char text-start)
                                         (save-restriction
                                           (narrow-to-region (point-min)
                                                             word-start)
                                           (and (re-search-backward (auto-capitalize-sentence-end)
                                                                    nil t)
                                                (= (match-end 0) text-start)
                                                ;; verify: preceded by
                                                ;; whitespace?
                                                (let ((previous-char
                                                       (char-after
                                                        (1- text-start))))
                                                  ;; In some modes, newline
                                                  ;; (^J, aka LFD) is comment-
                                                  ;; end, not whitespace:
                                                  (or (equal previous-char
                                                             ?\n)
                                                      (equal (char-syntax
                                                              previous-char)
                                                             ? )))
                                                ;; verify: not preceded by
                                                ;; an abbreviation?
                                                (let ((case-fold-search nil)
                                                      (abbrev-regexp
                                                       (if (featurep 'xemacs)
                                                           "\\<\\([A-Z�-��-�]?[a-z�-��-�]+\\.\\)+\\="
                                                         "\\<\\([[:upper:]]?[[:lower:]]+\\.\\)+\\=")))
                                                  (goto-char
                                                   (1+ (match-beginning 0)))
                                                  (or (not
                                                       (re-search-backward abbrev-regexp
                                                                           nil t))
                                                      (not
                                                       (member
                                                        (buffer-substring ; -no-properties?
                                                         (match-beginning 0)
                                                         (match-end 0))
                                                        auto-capitalize-words))))
                                                ))))
                                   ;; inserting lowercase text?
                                   (let ((case-fold-search nil))
                                     (goto-char word-start)
                                     (looking-at (if (featurep 'xemacs)
                                                     "[a-z�-��-�]+"
                                                   "[[:lower:]]+")))
                                   (or (eq auto-capitalize t)
                                       (prog1 (y-or-n-p
                                               (format "Capitalize \"%s\"? "
                                                       (buffer-substring
                                                        (match-beginning 0)
                                                        (match-end 0))))
                                         (message ""))))
                              ;; capitalize!
                              (undo-boundary)
                              (goto-char word-start)
                              (capitalize-word 1))))))))
            ((and auto-capitalize-yank
                  ;; `yank' sets `this-command' to t, and the
                  ;; after-change-functions are run before it has been
                  ;; reset:
                  (or (eq this-command 'yank)
                      (and (= length 0) ; insertion?
                           (eq this-command 't))))
             (save-excursion
               (goto-char beg)
               (save-match-data
                 (while (re-search-forward "\\Sw" end t)
                   ;; recursion!
                   (let* ((this-command 'self-insert-command)
                          (non-word-char (char-after (match-beginning 0)))
                          (last-command-event
                           (cond ((featurep 'xemacs) ; XEmacs
                                  (character-to-event non-word-char))
                                 (t non-word-char)))) ; GNU Emacs
                     (auto-capitalize (match-beginning 0)
                                      (match-end 0)
                                      0)))))))))
Constantine
  • 9,072
  • 1
  • 34
  • 49
  • It works perfectly! Many thanks. I suggest you submit your patch. According to http://melpa.org/#/auto-capitalize, this means just editing http://www.emacswiki.org/emacs/auto-capitalize.el. – scaramouche Nov 30 '14 at 00:04