;; This little hack makes it a bit easier to get colons in your programs. This
;; modifies the semicolon insert function such that if a string of semicolons
;; are followed by a non-whitespace character or a member of the list
;; *soft-semicolons-also-convert-on* but not a member of
;; *soft-semicolons-dont-convert-on*, then all of those semicolons are converted
;; into "full" colons.
;; Any command that is performed in between the typing of the last semicolon and
;; the typing of the next character will reset the string of semicolons that are
;; candidates for conversion to start at the current point.
;; This is primarily useful if you just dislike using the shift keys (like I do)
;; or because of the annoyance of reaching for the shift keys tend not to use
;; colons when they are optional (like using the purer keyword arguments in Loop
;; or Iterate over the ad-hoc keywords that actually polute the namespace).
;; This code is modified from GNU Emacs and, as such, is released under the
;; GPLv3 or later.
(defcustom *soft-semicolons-also-convert-on* '(9 tab)
"This variable marks characters that will trigger semicolon
conversion in addition to the non-whitespace printable
(defcustom *soft-semicolons-dont-convert-on* '(?( ?))
"This variable allows you to exclude certain characters from
(defun soft-semicolons (arg)
"Type semicolons like normal expect if they are immediately
followed by a non-whitespace character, in which case convert all
of the consequtive semicolons you were typing into colons."
(let ((keep-going t)
(let ((event (read-event)))
(cond ((equal 59 event)
;; A Semicolon, insert and keep going
(setq last-input-event nil))
((member event *soft-semicolons-dont-convert-on*)
;; For these special cases, don't do any conversion
(setq keep-going nil))
;; A non-whitespace printable character or something in
((or (member event *soft-semicolons-also-convert-on*)
(and (integerp event)
;; See if this is a printable character
(aref printable-chars event)
;; Rule out whitespace characters (which might also be
(not (member event '(9 10 13 32)))))
(let ((length (- (point) start-point)))
(delete-region start-point (point))
(insert-char 58 length))
;; ...and exit...
(setq keep-going nil))
;; Exit on anything else
(t (setq keep-going nil)))))
;; Push any residual command back onto unread-command-events to be read and
(setq unread-command-events (list last-input-event)))))
;; If you want to use this, you probably want to have something in your .emacs
;; file like:
;; (add-hook 'xxxx-mode-hook
;; (lambda ()
;; (define-key xxxx-mode-map (kbd ";") 'soft-semicolons)))
;; ...where "xxxx" might be replaced with "lisp", or "emacs-lisp", or
;; "lisp-interaction", or "slime", or all of them and more. You probably do not
;; want this to be a global binding as this will probably cause trouble with
;; something you wish to write sometime.