;;; names-dev.el --- Developer Functions to facilitate use of names.el with your package.
;; Copyright (C) 2014 Free Software Foundation, Inc.
;; Prefix: names
;; Separator: -
;;; Commentary:
;;
;; This package has some convenient functions for developers working
;; with names.el.
;; This package is installed along with names.el, but to use its
;; features you must require it explicitly:
;;
;; (require 'names-dev)
;;; License:
;;
;; This file is part of GNU Emacs.
;;
;; GNU Emacs 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 of the License, or
;; (at your option) any later version.
;;
;; GNU Emacs 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 GNU Emacs. If not, see .
;;; Code:
(require 'names)
(require 'elisp-mode nil t)
(require 'lisp-mode nil t)
;;; ---------------------------------------------------------------
;;; Developer Utility Functions
(defmacro names-compare-forms (name form-a form-b)
"Test if (namespace NAME FORM-A) is the same as FORM-B."
(declare (indent (lambda (&rest x) 0))
(debug (symbolp sexp form)))
`(equal
(macroexpand-all '(define-namespace ,name :global :verbose ,form-a))
(macroexpand-all ',form-b)))
(defmacro names-compare-forms-assert (name form-a form-b)
"Assert if (namespace NAME FORM-A) is the same as FORM-B."
(declare (indent (lambda (&rest x) 0))
(debug (symbolp sexp form)))
(cl-assert
(names-compare-forms name form-a form-b)
t))
(defmacro names-print (name &rest forms)
"Return the expanded results of (namespace NAME :global :verbose FORMS).
Ideal for determining why a specific form isn't being parsed
correctly. You may need to set `eval-expression-print-level' and
`eval-expression-print-length' to nil in order to see your full
expansion."
(declare (indent (lambda (&rest x) 0)) (debug 0))
`(define-namespace ,name :global :verbose ,@forms))
(defvar names-font-lock
'(("^:autoload\\_>" 0 'font-lock-warning-face prepend)
("(\\(\\_\\)[\t \n]+\\([^\t \n]+\\)"
(1 'font-lock-keyword-face)
(2 'font-lock-variable-name-face))))
(when (boundp 'lisp-el-font-lock-keywords-2)
(setq lisp-el-font-lock-keywords-2
(append names-font-lock
lisp-el-font-lock-keywords-2)))
;;; The backbone
(defun names--looking-at-namespace ()
"Non-nil if point is at a `define-namespace' form or an alias to it."
(when (looking-at "(\\_<")
(save-excursion
(forward-char 1)
(ignore-errors
(equal (indirect-function (intern (thing-at-point 'symbol)))
(indirect-function 'define-namespace))))))
(defun names--generate-new-buffer (name &optional form)
"Generate and return a new buffer.
NAME is current namespace name.
If FORM is provided, also try to use it to decide an informative
buffer name."
(get-buffer-create
(concat
" *names "
(format "%s %s"
(or (car-safe form) (random 10000))
(or (car-safe (cdr-safe form)) (random 10000)))
"*")))
(defmacro names--wrapped-in-namespace (command form &optional kill &rest body)
"Call COMMAND, except in a namespace.
In a namespace, expand FORM in a separate buffer then execute
BODY. If BODY is nil, call COMMAND instead.
If KILL is non-nil, kill the temp buffer afterwards."
(declare (indent defun)
(debug (sexp form form body)))
;; Get the namespace, if we're in one.
`(let ((evaled-form ,form)
(invocation
',(if (commandp command t)
`(call-interactively #',command)
command))
(entire-namespace
(save-excursion
(when (names--top-of-namespace)
(cdr (read (current-buffer))))))
b keylist spec name expanded-form)
;; If we're not in a namespace, call the regular `eval-defun'.
(if (null entire-namespace)
(eval invocation)
;; If we are, expand the function in a temp buffer
(setq name (pop entire-namespace))
(while (setq spec (names--next-keyword entire-namespace))
(setq keylist (append keylist spec)))
;; Prepare the (possibly) temporary buffer.
(setq b (names--generate-new-buffer name evaled-form))
(unwind-protect
(with-current-buffer b
(cl-letf (((symbol-function #'message) #'ignore))
(erase-buffer)
(emacs-lisp-mode)
;; Print everything inside the `progn'.
(mapc
(lambda (it) (pp it (current-buffer)))
(cdr
(setq expanded-form
(macroexpand
`(define-namespace ,name :global :clean-output ,@keylist ,evaled-form)))))
(when (fboundp 'font-lock-ensure)
(font-lock-ensure)))
;; Return value
,@(or body '((eval invocation))))
;; Kill the buffer if we won't need it.
(when (and ,kill (buffer-live-p b))
(kill-buffer b))))))
(defun names--top-of-namespace ()
"Move to the top of current namespace, and return non-nil.
If not inside a namespace, return nil and don't move point."
(let ((top (save-excursion
(beginning-of-defun)
(ignore-errors
(backward-up-list))
(when (names--looking-at-namespace)
(point)))))
(when top
(goto-char top)
t)))
(defun names-eval-defun (edebug-it)
"Identical to `eval-defun', except it works for forms inside namespaces.
Argument EDEBUG-IT is the same as `eval-defun', causes the form
to be edebugged."
(interactive "P")
(require 'font-lock) ; just in case
(let ((form
(save-excursion
(end-of-defun)
(beginning-of-defun)
(read (current-buffer)))))
(names--wrapped-in-namespace
eval-defun form (null edebug-it))))
;;; eval-last-sexp
(defalias 'names--preceding-sexp-original
(if (fboundp 'elisp--preceding-sexp)
(symbol-function 'elisp--preceding-sexp)
(symbol-function 'preceding-sexp)))
(defun names--preceding-sexp ()
"Like `elisp--preceding-sexp', but expand namespaces."
(names--wrapped-in-namespace
(names--preceding-sexp-original) (names--preceding-sexp-original) t
expanded-form))
(defun names-eval-last-sexp (eval-last-sexp-arg-internal)
"Identical to `eval-last-sexp', except it works for forms inside namespaces.
Argument EVAL-LAST-SEXP-ARG-INTERNAL is the same as `eval-last-sexp'."
(interactive "P")
(cl-letf (((symbol-function 'elisp--preceding-sexp) #'names--preceding-sexp)
((symbol-function 'preceding-sexp) #'names--preceding-sexp))
(eval-last-sexp eval-last-sexp-arg-internal)))
(defun names-eval-print-last-sexp (eval-last-sexp-arg-internal)
"Identical to `eval-print-last-sexp', except it works for forms inside namespaces.
Argument EVAL-LAST-SEXP-ARG-INTERNAL is the same as `eval-print-last-sexp'."
(interactive "P")
(cl-letf (((symbol-function 'elisp--preceding-sexp) #'names--preceding-sexp)
((symbol-function 'preceding-sexp) #'names--preceding-sexp))
(eval-print-last-sexp eval-last-sexp-arg-internal)))
;; (pp (symbol-function 'names--preceding-sexp-original) (current-buffer))
(defun names-pprint ()
"Pretty-print an expansion of the namespace around point."
(interactive)
(save-excursion
(when (names--top-of-namespace)
(let ((ns (cdr (read (current-buffer)))))
(pp-macroexpand-expression
(macroexpand (cons 'names-print ns)))))))
;;; Find stuff
(require 'find-func nil t)
(defalias 'names--fboundp-original (symbol-function 'fboundp))
(defalias 'names--boundp-original (symbol-function 'boundp))
(defalias 'names--find-function-read-original (symbol-function 'find-function-read))
(defalias 'find-function-read 'names--find-function-read)
(defun names--find-function-read (&optional type)
"Identical to `find-function-read', except it works inside namespaces."
(let ((buf (current-buffer)))
(names--wrapped-in-namespace
(names--find-function-read-original type) nil t
(set-buffer buf)
(let ((names--name name))
(cl-letf (((symbol-function 'fboundp) #'names--dev-fboundp)
((symbol-function 'boundp) #'names--dev-boundp))
(names--find-function-read-original type))))))
(defun names--dev-fboundp (sym)
(or (names--fboundp-original sym)
(names--fboundp-original (names--prepend sym))))
(defun names--dev-boundp (sym)
(or (names--boundp-original sym)
(names--boundp-original (names--prepend sym))))
;;; The keys
(eval-after-load 'lisp-mode
'(let ((map emacs-lisp-mode-map))
(define-key map [remap eval-defun] #'names-eval-defun)
(define-key map [remap eval-last-sexp] #'names-eval-last-sexp)
(define-key map [remap eval-print-last-sexp] #'names-eval-print-last-sexp)))
(provide 'names-dev)
;;; names-dev.el ends here