;;; easy-namespace.el --- elisp namespace helper -*- lexical-binding: t; -*- ;; Copyright (C) 2021 John Ankarström ;; Author: John Ankarström ;; Created: 1 Dec 2021 ;; Version: 0.1 ;; Keywords: lisp ;; URL: http://ankarstrom.se/~john/emacs/easy-namespace.el ;; This file is not part of GNU Emacs. ;; Permission to use, copy, modify and/or ;; distribute this software for any purpose ;; with or without fee is hereby granted. ;;; Commentary: ;; easy-namespace-mode is a minor mode for managing namespaces in ;; Emacs Lisp. It allows for the creation of namespace "aliases", ;; which automatically expand to a full package prefix when typed. ;; Additionally, by default, the full prefixes is visually replaced by ;; their corresponding aliases. ;; To install easy-namespace-mode, download this file, put it in ;; ~/.emacs.d/lisp and add the following to your configuration: ;; (add-to-list 'load-path (expand-file-name "~/.emacs.d/lisp")) ;; ------------------------------------------------------------------- ;; Detailed explanation ;; ------------------------------------------------------------------- ;; Emacs Lisp does not support namespaces as such, and all functions ;; and non-lexical variables live in the same scope, but it is common ;; practice for package authors to prefix all non-lexicals with a ;; package-specific prefix. Normally, the prefix ends with a single ;; hyphen for "public" symbols and two hyphens for "private" symbols. ;; For example, easy-namespace.el uses the prefix easy-namespace, ;; followed by 1-2 hyphens. ;; The problem is that long prefixes like easy-namespace are annoying ;; to have to type over and over again. Various solutions have been ;; proposed, such as adding proper namespaces to Emacs Lisp or using ;; macros to automatically add prefixes at compile time. The former ;; has been rejected by Richard Stallman, among others, with good ;; reason. The latter breaks useful commands like ;; `find-function-at-point'. ;; easy-namespace-mode is an alternative approach. It ;; i) visually replaces namespace prefixes with short alises in the ;; names of symbols, and ;; ii) automatically expands aliases when typed to the full namespace ;; prefix. ;; i) reduces code clutter, while ii) reduces the effort required on ;; the part of the package author, all without breaking commands like ;; `find-function-at-point' (or, indeed, without actually adding ;; namespaces to Emacs Lisp). ;; i) is disabled if `easy-namespace-show-full' is non-nil. ii) may be ;; temporarily disabled by using C-q (M-x quoted-insert) to insert the ;; character following the hyphen. ;; ------------------------------------------------------------------- ;; Usage ;; ------------------------------------------------------------------- ;; Namespace aliases are defined in `easy-namespace-alist'. The ;; following associates the namespace prefix reversible-mark with the ;; alias rm: ;; (add-to-list 'easy-namespace-alist '(reversible-mark . rm)) ;; With such a configuration, symbols such as reversible-mark-pop will ;; be visually displayed as rm-mark-pop as long as easy-namespace-mode ;; is active. Furthermore, when the user types rm-pop, it will be ;; automatically replaced with reversible-mark-pop. ;; To use a special namespace alias in a specific file, define ;; `easy-namespace-local-prefix' and `easy-namespace-local-alias' as ;; file-local variables: ;; ;; Local Variables: ;; ;; easy-namespace-local-prefix: reversible-mark ;; ;; easy-namespace-local-alias: rm ;; ;; End: ;; This is useful when writing and maintaining Emacs Lisp packages. ;; To activate easy-namespace-mode with emacs-lisp-mode, add the ;; following to your configuration: ;; (autoload 'easy-namespace-mode "easy-namespace" nil t) ;; (add-hook 'emacs-lisp-mode #'easy-namespace-mode) ;; Although only the expansion feature is supported, ;; easy-namespace-mode may also be used in the minibuffer: ;; (add-hook 'eval-expression-minibuffer-setup-hook #'easy-namespace-mode) ;; ------------------------------------------------------------------- ;; Caveats ;; ------------------------------------------------------------------- ;; Note that aliases also take effect within comments, strings and ;; quoted lists. This is very useful, but may be surprising to ;; first-time users. The alias is visually distinct from the rest of ;; the text, as it is styled with the face `easy-namespace-alias', ;; which has a dark gray foreground by default. ;;; Code: ;;;###autoload (define-minor-mode easy-namespace-mode "Minor mode for managing Emacs Lisp namespaces (i.e., package prefixes)." nil " Easy" (make-sparse-keymap) (if easy-namespace-mode (progn (font-lock-add-keywords nil easy-namespace--font-lock-keywords t) (add-to-list 'font-lock-extra-managed-props 'display) (font-lock-fontify-buffer) (advice-add 'self-insert-command :before #'easy-namespace--before-self-insert-command)) (font-lock-remove-keywords nil easy-namespace--font-lock-keywords) (font-lock-fontify-buffer) (advice-remove 'self-insert-command #'easy-namespace--before-self-insert-command))) (defconst easy-namespace--font-lock-keywords '((easy-namespace--find-prefix (1 (list 'face 'easy-namespace-alias 'display easy-namespace--alias-for-match 'help-echo (match-string 1)) prepend)))) (defvar easy-namespace--alias-for-match nil "Alias corresponding to currently matched namespace prefix.") (defvar-local easy-namespace-local-alias nil "A buffer-local namespace alias to be recorded in addition to those listed in `easy-namespace-alist'. Must be a symbol.") (defvar-local easy-namespace-local-prefix nil "A buffer-local namespace prefix to be recorded in addition to those listed in `easy-namespace-alist'. Must be a symbol.") (defcustom easy-namespace-alist nil "Association list of namespaces and aliases. Each entry has the form (PREFIX . ALIAS). Example: '((easy-namespace . en) (reversible-mark . rm)) When typed, an alias expands to the full namespace prefix. The expansion only takes place when semantically sound. Unless `easy-namespace-show-full' is non-nil, aliases visually replace their corresponding namespace prefixes while `easy-namespace-mode' is active." :type '(alist :key-type symbol :value-type symbol) :group 'easy-namespace) (defcustom easy-namespace-show-full nil "Show full namespaces, i.e., don't visually replace namespace prefixes with aliases. Automatic text expansion of aliases will still apply. See `easy-namespace-alist'." :type 'boolean :group 'easy-namespace) (defface easy-namespace-alias '((t :foreground "dark gray")) "Face for namespace replacement string. See `easy-namespace-alist'.") (defun easy-namespace--full-alist () (if easy-namespace-local-prefix (cons (cons easy-namespace-local-prefix easy-namespace-local-alias) easy-namespace-alist) easy-namespace-alist)) (defun easy-namespace--before-self-insert-command (&rest args) (when (and easy-namespace-mode (characterp last-input-event) (string-match-p "[^][)(#',`[:blank:]\n\"]" (char-to-string last-input-event)) (looking-back (concat "\\(?:^\\|[][)(#',`[:blank:]\n\"]\\)" (regexp-opt (map-apply (lambda (k v) (symbol-name v)) (easy-namespace--full-alist)) "\\(") "-") nil t)) (let ((alias (intern (match-string 1)))) (delete-region (1+ (match-beginning 0)) (match-end 0)) (insert (symbol-name (car (rassoc alias (easy-namespace--full-alist))))) (insert "-")))) (defun easy-namespace--find-prefix (limit) (when (and (not easy-namespace-show-full) (re-search-forward (concat "\\(?:^\\|[][)(#',`[:blank:]\n\"]\\)" (regexp-opt (map-apply (lambda (k v) (symbol-name k)) (easy-namespace--full-alist)) "\\(") "\\(-+\\)[^][)(#',`[:blank:]\n\"]+") limit t)) (when-let ((alias (cdr (assoc (intern (match-string 1)) (easy-namespace--full-alist))))) (setq easy-namespace--alias-for-match (symbol-name alias))))) (provide 'easy-namespace) ;;; easy-namespace.el ends here ;; Local Variables: ;; easy-namespace-local-prefix: easy-namespace ;; easy-namespace-local-alias: en ;; End: