A new mode for Python documentation
Posted December 20, 2014 at 03:02 PM | categories: python, emacs | tags:
Updated December 22, 2014 at 10:12 AM
http://www.youtube.com/watch?v=G_r7wTcVK54 , and see the latest source at https://github.com/jkitchin/jmax/blob/master/pydoc.el .
update: See this in action atThe emacs-lisp documentation in Emacs is inspiring. It is interlinked, you can click on links to open source files, other commands, etc… Python documentation is not that nice. It should be.
I wrote a little pydoc function:
(defun pydoc (name) "Display pydoc information for NAME in a buffer named *pydoc*." (interactive "sName of function or module: ") (switch-to-buffer-other-window "*pydoc*") (erase-buffer) (insert (shell-command-to-string (format "python -m pydoc %s" name))) (goto-char (point-min)))
which at least accesses python documentation in emacs. It looks like this:
But, this lacks functionality. I want there to be useful links in this, so I can click on the filename to open the source, or click on the packages to get their documentation. Below, we walk through a few functions that will operate on the buffer and put text properties on different pieces.
First, let us make the source file clickable so it opens the source.
(defun pydoc-make-file-link () "Find FILE in a pydoc buffer and make it a clickable link" (goto-char (point-min)) (when (re-search-forward "^FILE \\(.*\\)$" nil t) (let ((map (make-sparse-keymap)) (start (match-beginning 1)) (end (match-end 1)) (source-file (match-string 1))) ;; set file to be clickable to open the source (define-key map [mouse-1] `(lambda () (interactive) (find-file ,source-file))) (set-text-properties start end `(local-map, map font-lock-face (:foreground "blue" :underline t) mouse-face highlight help-echo "mouse-1: click to open")))))
pydoc-make-file-link
Next, sometimes there are URLs in the python documentation. These should all open up in a browser when you click on them. Here we propertize anything we recognize as a URL to make it open when clicked on.
(defun pydoc-make-url-links () (goto-char (point-min)) (while (re-search-forward "\\(http\\(s\\)?://.*$\\)" nil t) (let ((map (make-sparse-keymap)) (start (match-beginning 1)) (end (match-end 1))) (define-key map [mouse-1] `(lambda () (interactive) (browse-url ,(buffer-substring start end)))) (set-text-properties start end `(local-map ,map font-lock-face (:foreground "blue" :underline t) mouse-face highlight help-echo (format "mouse-1: click to open"))))))
When we get documentation for a package, we should make each entry of the package clickable, so we can get to the documentation for that package easily. We store the name of the current package so we can construct the path to the subpackage.
(defun pydoc-get-name () "get NAME and store locally" (make-variable-buffer-local 'pydoc-name) (goto-char (point-min)) (when (re-search-forward "^NAME \\s-*\\([^-][a-zA-Z]*\\)" nil t) (setq pydoc-name (match-string 1)))) (defun pydoc-make-package-links () "make links in PACKAGE CONTENTS" (goto-char (point-min)) (when (re-search-forward "^PACKAGE CONTENTS" nil t) (forward-line) (while (string-match "^ \\([a-zA-Z0-9_]*\\)[ ]?\\((package)\\)?" (buffer-substring (line-beginning-position) (line-end-position))) (let ((map (make-sparse-keymap)) (start (match-beginning 1)) (end (match-end 1)) (package (concat pydoc-name "." (match-string 1 (buffer-substring (line-beginning-position) (line-end-position)))))) (define-key map [mouse-1] `(lambda () (interactive) (pydoc ,package))) (set-text-properties (+ (line-beginning-position) start) (+ (line-beginning-position) end) `(local-map, map font-lock-face (:foreground "blue" :underline t) mouse-face highlight help-echo (format "mouse-1: click to open %s" ,package)))) (forward-line))))
Next, we put some eye candy on function names and arguments. This won't do anything functionally, but it breaks up the monotony of all black text.
(defun pydoc-colorize-functions () "Change color of function names and args." (goto-char (point-min)) (when (re-search-forward "^Functions" nil t) (while (re-search-forward " \\([a-zA-z0-9-]+\\)(\\([^)]*\\))" nil t) (set-text-properties (match-beginning 1) (match-end 1) '(font-lock-face (:foreground "brown"))) (set-text-properties (match-beginning 2) (match-end 2) '(font-lock-face (:foreground "red"))))))
I have gotten used to the [back] link in emacs-lisp documentation, so we try to emulate it here.
(defun pydoc-insert-back-link () "Insert link to previous buffer" (goto-char (point-max)) (insert " [back]") (let ((map (make-sparse-keymap))) ;; set file to be clickable to open the source (define-key map [mouse-1] (lambda () (interactive) (pydoc *pydoc-last*))) (set-text-properties (line-beginning-position) (line-end-position) `(local-map, map font-lock-face (:foreground "blue" :underline t) mouse-face highlight help-echo "mouse-1: click to return"))))
pydoc-insert-back-link
Ok, finally we remake the pydoc function.
(defvar *pydoc-current* nil "Stores current pydoc command") (defvar *pydoc-last* nil "Stores the last pydoc command") (defun pydoc (name) "Display pydoc information for NAME in a buffer named *pydoc*." (interactive "sName of function or module: ") (switch-to-buffer-other-window "*pydoc*") (setq buffer-read-only nil) (erase-buffer) (insert (shell-command-to-string (format "python -m pydoc %s" name))) (goto-char (point-min)) ;; save (when *pydoc-current* (setq *pydoc-last* *pydoc-current*)) (setq *pydoc-current* name) (save-excursion (pydoc-get-name) (pydoc-make-url-links) (pydoc-make-file-link) (pydoc-make-package-links) (pydoc-colorize-functions) (pydoc-insert-back-link)) ;; make read-only and press q to quit (setq buffer-read-only t) (use-local-map (copy-keymap org-mode-map)) (local-set-key "q" #'(lambda () (interactive) (kill-buffer))) (font-lock-mode))
pydoc
Now, we get a much more functional pydoc:
Figure 2: Annotated screenshot
and with the colorized function names:
Admittedly, there seems to be a lot of boilerplate code for propertizing the strings, but it doesn't seem too bad. I will probably use this documentation tool this spring, so maybe I will think of new functionality to add to pydoc. Any ideas?
Copyright (C) 2014 by John Kitchin. See the License for information about copying.
Org-mode version = 8.2.10