406 lines
13 KiB
EmacsLisp
406 lines
13 KiB
EmacsLisp
;;; electric-spacing.el --- Insert operators with surrounding spaces smartly
|
||
|
||
;; Copyright (C) 2004, 2005, 2007-2015 Free Software Foundation, Inc.
|
||
|
||
;; Author: William Xu <william.xwl@gmail.com>
|
||
;; Version: 5.0
|
||
;; Package-Version: 20151209.736
|
||
|
||
;; This program is free software; you can redistribute it and/or modify
|
||
;; it under the terms of the GNU General Public License as published by
|
||
;; the Free Software Foundation; either version 3, or (at your option)
|
||
;; any later version.
|
||
|
||
;; This program is distributed in the hope that it will be useful, but
|
||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
;; General Public License for more details.
|
||
|
||
;; You should have received a copy of the GNU General Public License
|
||
;; along with EMMS; see the file COPYING. If not, write to the
|
||
;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||
;; Boston, MA 02110-1301, USA.
|
||
|
||
;;; Commentary:
|
||
|
||
;; Smart Operator mode is a minor mode which automatically inserts
|
||
;; surrounding spaces around operator symbols. For example, `='
|
||
;; becomes ` = ', `+=' becomes ` += '. This is most handy for writing
|
||
;; C-style source code.
|
||
;;
|
||
;; Type `M-x electric-spacing-mode' to toggle this minor mode.
|
||
|
||
;;; Acknowledgements
|
||
|
||
;; Nikolaj Schumacher <n_schumacher@web.de>, for suggesting
|
||
;; reimplementing as a minor mode and providing an initial patch for
|
||
;; that.
|
||
|
||
;;; Code:
|
||
|
||
(require 'cc-mode)
|
||
(require 'thingatpt)
|
||
|
||
;;; electric-spacing minor mode
|
||
|
||
(defcustom electric-spacing-double-space-docs t
|
||
"Enable double spacing of . in document lines - e,g, type '.' => get '. '."
|
||
:type 'boolean
|
||
:group 'electricity)
|
||
|
||
(defcustom electric-spacing-docs t
|
||
"Enable electric-spacing in strings and comments."
|
||
:type 'boolean
|
||
:group 'electricity)
|
||
|
||
(defvar electric-spacing-rules
|
||
'((?= . electric-spacing-self-insert-command)
|
||
(?< . electric-spacing-<)
|
||
(?> . electric-spacing->)
|
||
(?% . electric-spacing-%)
|
||
(?+ . electric-spacing-+)
|
||
(?- . electric-spacing--)
|
||
(?* . electric-spacing-*)
|
||
(?/ . electric-spacing-/)
|
||
(?& . electric-spacing-&)
|
||
(?| . electric-spacing-self-insert-command)
|
||
(?: . electric-spacing-:)
|
||
(?? . electric-spacing-?)
|
||
(?, . electric-spacing-\,)
|
||
(?~ . electric-spacing-~)
|
||
(?. . electric-spacing-.)
|
||
(?^ . electric-spacing-self-insert-command)))
|
||
|
||
(defun electric-spacing-post-self-insert-function ()
|
||
(when (electric-spacing-should-run?)
|
||
(let ((rule (cdr (assq last-command-event electric-spacing-rules))))
|
||
(when rule
|
||
(goto-char (electric--after-char-pos))
|
||
(delete-char -1)
|
||
(funcall rule)))))
|
||
|
||
|
||
;;;###autoload
|
||
(define-minor-mode electric-spacing-mode
|
||
"Toggle automatic surrounding space insertion (Electric Spacing mode).
|
||
With a prefix argument ARG, enable Electric Spacing mode if ARG is
|
||
positive, and disable it otherwise. If called from Lisp, enable
|
||
the mode if ARG is omitted or nil.
|
||
|
||
This is a local minor mode. When enabled, typing an operator automatically
|
||
inserts surrounding spaces. e.g., `=' becomes ` = ',`+=' becomes ` += '. This
|
||
is very handy for many programming languages."
|
||
:global nil
|
||
:group 'electricity
|
||
:lighter " _+_"
|
||
|
||
;; body
|
||
(if electric-spacing-mode
|
||
(add-hook 'post-self-insert-hook
|
||
#'electric-spacing-post-self-insert-function nil t)
|
||
(remove-hook 'post-self-insert-hook
|
||
#'electric-spacing-post-self-insert-function t)))
|
||
|
||
(defun electric-spacing-self-insert-command ()
|
||
"Insert character with surrounding spaces."
|
||
(electric-spacing-insert (string last-command-event)))
|
||
|
||
(defun electric-spacing-insert (op &optional only-where)
|
||
"See `electric-spacing-insert-1'."
|
||
(delete-horizontal-space)
|
||
(cond ((and (electric-spacing-lispy-mode?)
|
||
(not (electric-spacing-document?)))
|
||
(electric-spacing-lispy op))
|
||
(t
|
||
(electric-spacing-insert-1 op only-where))))
|
||
|
||
(defun electric-spacing-insert-1 (op &optional only-where)
|
||
"Insert operator OP with surrounding spaces.
|
||
e.g., `=' becomes ` = ', `+=' becomes ` += '.
|
||
|
||
When `only-where' is 'after, we will insert space at back only;
|
||
when `only-where' is 'before, we will insert space at front only;
|
||
when `only-where' is 'middle, we will not insert space."
|
||
(pcase only-where
|
||
(`before (insert " " op))
|
||
(`middle (insert op))
|
||
(`after (insert op " "))
|
||
(_
|
||
(let ((begin? (bolp)))
|
||
(unless (or (looking-back (regexp-opt
|
||
(mapcar 'char-to-string
|
||
(mapcar 'car electric-spacing-rules)))
|
||
(line-beginning-position))
|
||
begin?)
|
||
(insert " "))
|
||
(insert op " ")
|
||
(when begin?
|
||
(indent-according-to-mode))))))
|
||
|
||
(defun electric-spacing-c-types ()
|
||
(concat c-primitive-type-key "?"))
|
||
|
||
(defun electric-spacing-document? ()
|
||
(nth 8 (syntax-ppss)))
|
||
|
||
(defun electric-spacing-should-run? ()
|
||
(or (not electric-spacing-docs)
|
||
(not (electric-spacing-document?))))
|
||
|
||
(defun electric-spacing-lispy-mode? ()
|
||
(derived-mode-p 'emacs-lisp-mode
|
||
'lisp-mode
|
||
'lisp-interaction-mode
|
||
'scheme-mode))
|
||
|
||
(defun electric-spacing-lispy (op)
|
||
"We're in a Lisp-ish mode, so let's look for parenthesis.
|
||
Meanwhile, if not found after ( operators are more likely to be function names,
|
||
so let's not get too insert-happy."
|
||
(cond
|
||
((save-excursion
|
||
(backward-char 1)
|
||
(looking-at "("))
|
||
(if (equal op ",")
|
||
(electric-spacing-insert-1 op 'middle)
|
||
(electric-spacing-insert-1 op 'after)))
|
||
((equal op ",")
|
||
(electric-spacing-insert-1 op 'before))
|
||
(t
|
||
(electric-spacing-insert-1 op 'middle))))
|
||
|
||
(defconst electric-spacing-operators-regexp
|
||
(regexp-opt
|
||
(mapcar (lambda (el) (char-to-string (car el)))
|
||
electric-spacing-rules)))
|
||
|
||
|
||
;;; Fine Tunings
|
||
|
||
(defun electric-spacing-< ()
|
||
"See `electric-spacing-insert'."
|
||
(cond
|
||
((or (and c-buffer-is-cc-mode
|
||
(looking-back
|
||
(concat "\\("
|
||
(regexp-opt
|
||
'("#include" "vector" "deque" "list" "map" "stack"
|
||
"multimap" "set" "hash_map" "iterator" "template"
|
||
"pair" "auto_ptr" "static_cast"
|
||
"dynmaic_cast" "const_cast" "reintepret_cast"
|
||
|
||
"#import"))
|
||
"\\)\\ *")
|
||
(line-beginning-position)))
|
||
(derived-mode-p 'sgml-mode))
|
||
(insert "<>")
|
||
(backward-char))
|
||
(t
|
||
(electric-spacing-insert "<"))))
|
||
|
||
(defun electric-spacing-: ()
|
||
"See `electric-spacing-insert'."
|
||
(cond (c-buffer-is-cc-mode
|
||
(if (looking-back "\\?.+")
|
||
(electric-spacing-insert ":")
|
||
(electric-spacing-insert ":" 'middle)))
|
||
((derived-mode-p 'haskell-mode)
|
||
(electric-spacing-insert ":"))
|
||
((derived-mode-p 'python-mode) (electric-spacing-python-:))
|
||
((derived-mode-p 'ess-mode)
|
||
(insert ":"))
|
||
(t
|
||
(electric-spacing-insert ":" 'after))))
|
||
|
||
(defun electric-spacing-\, ()
|
||
"See `electric-spacing-insert'."
|
||
(electric-spacing-insert "," 'after))
|
||
|
||
(defun electric-spacing-. ()
|
||
"See `electric-spacing-insert'."
|
||
(cond ((and electric-spacing-double-space-docs
|
||
(electric-spacing-document?))
|
||
(electric-spacing-insert "." 'after)
|
||
(insert " "))
|
||
((or (looking-back "[0-9]")
|
||
(or (and c-buffer-is-cc-mode
|
||
(looking-back "[a-z]"))
|
||
(and
|
||
(derived-mode-p 'python-mode 'ruby-mode)
|
||
(looking-back "[a-z\)]"))
|
||
(and
|
||
(derived-mode-p 'js-mode 'js2-mode)
|
||
(looking-back "[a-z\)$]"))))
|
||
(insert "."))
|
||
((derived-mode-p 'cperl-mode 'perl-mode 'ruby-mode)
|
||
;; Check for the .. range operator
|
||
(if (looking-back ".")
|
||
(insert ".")
|
||
(insert " . ")))
|
||
(t
|
||
(electric-spacing-insert "." 'after)
|
||
(insert " "))))
|
||
|
||
(defun electric-spacing-& ()
|
||
"See `electric-spacing-insert'."
|
||
(cond (c-buffer-is-cc-mode
|
||
;; ,----[ cases ]
|
||
;; | char &a = b; // FIXME
|
||
;; | void foo(const int& a);
|
||
;; | char *a = &b;
|
||
;; | int c = a & b;
|
||
;; | a && b;
|
||
;; `----
|
||
(cond ((looking-back (concat (electric-spacing-c-types) " *" ))
|
||
(electric-spacing-insert "&" 'after))
|
||
((looking-back "= *")
|
||
(electric-spacing-insert "&" 'before))
|
||
(t
|
||
(electric-spacing-insert "&"))))
|
||
(t
|
||
(electric-spacing-insert "&"))))
|
||
|
||
(defun electric-spacing-* ()
|
||
"See `electric-spacing-insert'."
|
||
(cond (c-buffer-is-cc-mode
|
||
;; ,----
|
||
;; | a * b;
|
||
;; | char *a;
|
||
;; | char **b;
|
||
;; | (*a)->func();
|
||
;; | *p++;
|
||
;; | *a = *b;
|
||
;; `----
|
||
(cond ((looking-back (concat (electric-spacing-c-types) " *" ))
|
||
(electric-spacing-insert "*" 'before))
|
||
((looking-back "\\* *")
|
||
(electric-spacing-insert "*" 'middle))
|
||
((looking-back "^[ (]*")
|
||
(electric-spacing-insert "*" 'middle)
|
||
(indent-according-to-mode))
|
||
((looking-back "= *")
|
||
(electric-spacing-insert "*" 'before))
|
||
(t
|
||
(electric-spacing-insert "*"))))
|
||
|
||
;; Handle python *args and **kwargs
|
||
((derived-mode-p 'python-mode)
|
||
;; Can only occur after '(' ',' or on a new line, so just check
|
||
;; for those. If it's just after a comma then also insert a space
|
||
;; before the *.
|
||
(cond ((looking-back ",") (insert " *"))
|
||
((looking-back "[(,^)][ \t]*[*]?") (insert "*"))
|
||
;; Othewise act as normal
|
||
(t (electric-spacing-insert "*"))))
|
||
(t
|
||
(electric-spacing-insert "*"))))
|
||
|
||
(defun electric-spacing-> ()
|
||
"See `electric-spacing-insert'."
|
||
(cond ((and c-buffer-is-cc-mode (looking-back " - "))
|
||
(delete-char -3)
|
||
(insert "->"))
|
||
(t
|
||
(electric-spacing-insert ">"))))
|
||
|
||
(defun electric-spacing-+ ()
|
||
"See `electric-spacing-insert'."
|
||
(cond ((and c-buffer-is-cc-mode (looking-back "\\+ *"))
|
||
(when (looking-back "[a-zA-Z0-9_] +\\+ *")
|
||
(save-excursion
|
||
(backward-char 2)
|
||
(delete-horizontal-space)))
|
||
(electric-spacing-insert "+" 'middle)
|
||
(indent-according-to-mode))
|
||
(t
|
||
(electric-spacing-insert "+"))))
|
||
|
||
(defun electric-spacing-- ()
|
||
"See `electric-spacing-insert'."
|
||
(cond ((and c-buffer-is-cc-mode (looking-back "\\- *"))
|
||
(when (looking-back "[a-zA-Z0-9_] +\\- *")
|
||
(save-excursion
|
||
(backward-char 2)
|
||
(delete-horizontal-space)))
|
||
(electric-spacing-insert "-" 'middle)
|
||
(indent-according-to-mode))
|
||
|
||
;; exponent notation, e.g. 1e-10: don't space
|
||
((looking-back "[0-9.]+[eE]")
|
||
(insert "-"))
|
||
|
||
;; a = -9
|
||
((and (looking-back (concat electric-spacing-operators-regexp " *"))
|
||
(not (looking-back "- *")))
|
||
(electric-spacing-insert "-" 'before))
|
||
|
||
(t
|
||
(electric-spacing-insert "-"))))
|
||
|
||
(defun electric-spacing-? ()
|
||
"See `electric-spacing-insert'."
|
||
(cond (c-buffer-is-cc-mode
|
||
(electric-spacing-insert "?"))
|
||
(t
|
||
(electric-spacing-insert "?" 'after))))
|
||
|
||
(defun electric-spacing-% ()
|
||
"See `electric-spacing-insert'."
|
||
(cond (c-buffer-is-cc-mode
|
||
;; ,----
|
||
;; | a % b;
|
||
;; | printf("%d %d\n", a % b);
|
||
;; `----
|
||
(if (and (looking-back "\".*")
|
||
(not (looking-back "\",.*")))
|
||
(insert "%")
|
||
(electric-spacing-insert "%")))
|
||
;; If this is a comment or string, we most likely
|
||
;; want no spaces - probably string formatting
|
||
((and (derived-mode-p 'python-mode)
|
||
(electric-spacing-document?))
|
||
(insert "%"))
|
||
(t
|
||
(electric-spacing-insert "%"))))
|
||
|
||
(defun electric-spacing-~ ()
|
||
"See `electric-spacing-insert'."
|
||
;; First class regex operator =~ langs
|
||
(cond ((derived-mode-p 'ruby-mode 'perl-mode 'cperl-mode)
|
||
(if (looking-back "= ")
|
||
(progn
|
||
(delete-char -2)
|
||
(insert "=~ "))
|
||
(insert "~")))
|
||
(t
|
||
(insert "~"))))
|
||
|
||
(defun electric-spacing-/ ()
|
||
"See `electric-spacing-insert'."
|
||
;; *nix shebangs #!
|
||
(cond ((and (eq 1 (line-number-at-pos))
|
||
(save-excursion
|
||
(move-beginning-of-line nil)
|
||
(looking-at "#!")))
|
||
(insert "/"))
|
||
(t
|
||
(electric-spacing-insert "/"))))
|
||
|
||
|
||
(defun electric-spacing-enclosing-paren ()
|
||
"Return the opening parenthesis of the enclosing parens, or nil if not inside any parens."
|
||
(interactive)
|
||
(let ((ppss (syntax-ppss)))
|
||
(when (nth 1 ppss)
|
||
(char-after (nth 1 ppss)))))
|
||
|
||
(defun electric-spacing-python-: ()
|
||
(if (and (not (in-string-p))
|
||
(eq (electric-spacing-enclosing-paren) ?\{))
|
||
(electric-spacing-insert ":" 'after)
|
||
(insert ":")))
|
||
|
||
(provide 'electric-spacing)
|
||
|
||
;;; electric-spacing.el ends here
|