Why does deleting the left parenthesis and inserting the dot cause
the "t" to capitalize?
By default, abbrev expansion preserves case.
The first expansion happens literally. The key to the expansion rule
("textedited" "textEdited" nil :count 0)
is precisely "textedited".
A parenthesis is considered a word-separator, thus "textEdited" is
inserted. When the parenthesis is deleted and the dot inserted, a
second expansion occurs because period is also a word-separator. In
this expansion, however, "textEdited" is acted on and it has a capital
letter.
(emacs) Dabbrev
Customization states,
Normally, dynamic abbrev expansion preserves the case pattern of the
dynamic abbrev you are expanding, by converting the expansion to
that case pattern.
The capital letter indicates to the expansion function that the case
should be a particular case pattern. It's not clear to me how Emacs
defines these case patterns; does it differentiate camelCase from
PascalCase (i.e. StudlyCaps)? It appears not. Hence, the "t" is capitalized.
Is there a way to prevent that?
Yes, several.
Using the case-fixed property keyword in the abbrev table
definition:
;;-*-coding: utf-8;-*-
(define-abbrev-table 'global-abbrev-table
'(
("textedited" "textEdited" nil :case-fixed t :count 0)
))
This works, but isn't practical for the described use case. The
add-global-abbrev command cannot be used to define abbrev
properties. You would need to manually edit the abbrev table
definition any time an abbrev was added.
It turns out that an abbrev table is what's called an
obarray.
These can have
properties themselves.
Another solution is to adjust the table property so that every entry is case-fixed:
(dolist (table abbrev-table-name-list)
(abbrev-table-put (symbol-value table) :case-fixed t))
This works, but isn't transparent. The table definition given at the
end-user level, that of the define-abbrev-table definition created
with write-abbrev-file, won't show the case-fixed property.
Further, this applies the case-fixed property to every abbrev in
the table. Do you want every table entry to have this property?
There are also several options, dabbrev-case-replace,
case-replace, dabbrev-case-fold-search, and case-fold-search,
described in (emacs) Dabbrev
Customization.
On my reading, these should help, but they don't. That's either a bug
or a misunderstanding by me.
So, it appears that case-fixed must be set on an individual abbrev to get
the desired behavior. For those already created, just add case-fixed manually. Otherwise, here's a (potentially over-engineered)
function to define abbrevs that allows control over the case-fixed
property:
(defun my-add-table-abbrev (name expansion &optional fixed table interp)
"Define abbrev with NAME and EXPANSION for last word(s) before point in TABLE.
FIXED sets case-fixed keyword property; default is nil. TABLE defaults to
`global-abbrev-table'.
Behaves similarly to `add-global-abbrev'. The prefix argument
specifies the number of words before point that form the
expansion; or zero means the region is the expansion. A negative
argument means to undefine the specified abbrev. This command
uses the minibuffer to read the abbreviation.
Abbrevs are overwritten without prompt when called from Lisp.
\(fn NAME EXPANSION &optional FIXED TABLE)"
;; define behavior when called interactively
(interactive
(let* ((arg (prefix-numeric-value current-prefix-arg)) ; get numeric prefix
;; note negative prefix returns exp as nil
(exp (and (>= arg 0) ; positive prefix grabs `arg` previous words
(buffer-substring-no-properties
(point)
(if (= arg 0) (mark) ; zero prefix grabs region
(save-excursion (forward-word (- arg)) (point))))))
;; when exp exists define name to create, otherwise name the one to destroy
(name (read-string (format (if exp "Abbev name: "
"Undefine abbrev: "))))
;; when exp, ask user for expansion
(expansion (and exp (read-string "Expansion: " exp)))
(table (symbol-value (intern-soft (completing-read
"Abbrev table (global-abbrev-table): "
abbrev-table-name-list nil t nil nil "global-abbrev-table"))))
;; don't ask for fixed case when deleting
(fixed (and exp (y-or-n-p (format "Fix case? ")))))
;; send args to function, note last element indicates interactive
(list name expansion fixed table t)))
;; end interactive behavior definition, start function definition
(let ((table (or table global-abbrev-table)) ; define default table
(fixed (or fixed nil))) ; define default case-fixed
(set-text-properties 0 (length name) nil name)
(set-text-properties 0 (length expansion) nil expansion)
(if (or (null expansion) ; there is expansion to set,
(not (abbrev-expansion name table)) ; the expansion is not already defined
(not interp) ; and we're not calling from
; code (calling interactively)
(y-or-n-p (format "%s expands to \"%s\"; redefine? "
name (abbrev-expansion name table))))
(define-abbrev table name expansion nil :case-fixed fixed))))