;;; usage-memo.el --- integration of Emacs help system and memo ;; $Id: usage-memo.el,v 1.6 2007/07/12 06:28:44 rubikitch Exp $ ;; Copyright (C) 2007 rubikitch ;; Author: rubikitch ;; Keywords: convenience, languages, lisp, help, tools, docs ;; URL:http://www.emacswiki.org/cgi-bin/wiki/download/usage-memo.el ;; This file 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 2, or (at your option) ;; any later version. ;; This file 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; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Commentary: ;; This program enables you to write annotation in *Help* and ;; third-party help systems. When we do programming, we often use ;; Emacs help system (ie. describe-function). Do you want to take a ;; note in the *Help* buffer and want Emacs to show your note later? ;; In other words, integration of Emacs help and your memo! ;; Annotation files are stored below ~/memo/umemo by default. Its ;; subdirectories are categories. And their subdirectories are entry ;; annotation files. Because annotation files are read in each case, ;; you can generate annotation files automatically. ;;; Supported help systems: ;; describe-function ;; describe-variable ;; slime-documentation (slime.el) ;; slime-describe-symbol (slime.el) ;; slime-describe-function (slime.el) ;; ri (ri-ruby.el) ;; lh-refe (langhelp / ReFe) ;; ;; usage-memo is general-purpose: it is easy to support other help systems. ;;; Installation: ;; Append to .emacs: ;; (require 'usage-memo) ;; (umemo-initialize) ;; If you support other help systems, add `define-usage-memo' sexps in ;; .emacs or redefine `umemo-initialize'. ;;; Usage: ;; * Switch to *Help*. ;; * Write your annotation below `===*===*===*===*===*===*===*===*===*===*===' line. ;; * Press C-x C-s to save annotation. ;; * Even if *Help* is killed or Emacs is restarted, ;; your annotation is shown when you look up the same entry!! ;;; Further information: ;; [EVAL IT] (describe-function 'define-usage-memo) ;; [EVAL IT] (describe-function 'usage-memo-mode) ;; [EVAL IT] (describe-variable 'umemo-base-directory) ;; [EVAL IT] (describe-function 'umemo-pathname) ;; [EVAL IT] (describe-variable 'umemo-category) ;; [EVAL IT] (describe-variable 'umemo-entry-name) ;; [EVAL IT] (describe-variable 'umemo-current-entry-name) ;;; History: ;; $Log: usage-memo.el,v $ ;; Revision 1.6 2007/07/12 06:28:44 rubikitch ;; usage-memo-mode-map: bind SPC .. ~ to self-insert-command ;; ;; Revision 1.5 2007/07/06 18:41:51 rubikitch ;; added available URL. ;; ;; Revision 1.4 2007/07/06 18:20:21 rubikitch ;; define-usage-memo: additional optional argument `name-converter-function' ;; ;; Use downcase filename for SLIME. ;; If you already use upcase filename, rename all the upcase filename to downcase. ;; `wdired-change-to-wdired-mode' is convenient to do this. ;; ;; Revision 1.3 2007/07/01 20:05:53 rubikitch ;; Support slime-describe-function. ;; Update docs. ;; First public release! ;; ;; Revision 1.2 2007/07/01 18:40:47 rubikitch ;; write docs. ;; ;; Revision 1.1 2007/07/01 10:07:15 rubikitch ;; Initial revision ;; ;;; Code: (require 'cl) (defvar umemo-base-directory "~/memo/umemo" "Directory where memo files are placed. You need not create directory when it does not exist. usage-memo creates it automatically.") (defvar umemo-separator "\n===*===*===*===*===*===*===*===*===*===*===\n" "Separator between documentation and memo.") (defvar umemo-category nil "Category of usage-memo. It is defined by `define-usage-memo'.") (defvar umemo-entry-name nil "Entry name that help buffer(per buffer) is showing. It is used as filename of annotation.") (defvar umemo-current-entry-name nil "Entry name that help buffer(globally) is showing. See also `umemo-entry-name'. It is needed because some help system (eg. SLIME) kills help buffer and re-create it. In this case, the buffer-local `umemo-entry-name' is cleared: I have no choice but to use global variable. But it is probaby rare case.") (make-variable-buffer-local 'umemo-category) (make-variable-buffer-local 'umemo-entry-name) ;;;; low-level utils (defun umemo-pathname (category entry-name) "Filename of annotation. `umemo-base-directory'/CATEGORY/ENTRY-NAME." (format "%s/%s/%s" umemo-base-directory category entry-name)) (defun umemo-fall-back-to-global-key-binding-in-memo-area (key) (let* ((lb (local-key-binding key)) (gb (global-key-binding key))) (call-interactively (cond ((umemo-point-is-in-memo-area-p (point)) gb) (lb lb) (t gb))))) (defun umemo-point-is-in-memo-area-p (point) (save-excursion (goto-char point) (search-backward umemo-separator nil t))) (defmacro umemo-with-append-to-buffer (buffer &rest body) "Execute BODY at the end of BUFFER." `(save-excursion (set-buffer buffer) (goto-char (point-max)) ,@body)) (defmacro umemo-if-buffer-has-separator (&rest body) `(save-excursion (goto-char (point-min)) (when (search-forward umemo-separator nil t) ,@body))) (defun umemo-has-entry-p () (save-excursion (goto-char (point-min)) (and (search-forward umemo-separator nil t) (/= (point) (point-max))))) ;;;; mid-level utils (defun umemo-insert-memo (category entry-name buffer) (let ((path (umemo-pathname category entry-name))) (umemo-with-append-to-buffer buffer (setq buffer-read-only nil) (unless (umemo-has-entry-p) (insert umemo-separator) (and (file-exists-p path) (insert-file-contents path)))))) (defun umemo-setup-variables (category entry-name buffer) (setq umemo-current-entry-name entry-name) (with-current-buffer buffer (setq umemo-category category umemo-entry-name entry-name))) ;;;; initializer (defun umemo-setup (category entry-name buffer) (when (get-buffer buffer) (with-current-buffer buffer (umemo-setup-variables category entry-name buffer) (umemo-insert-memo category entry-name buffer) (usage-memo-mode 1)))) ;;;; commands (defun umemo-save () "Save current usage memo(annotation) into file." (interactive) (umemo-if-buffer-has-separator (let* ((path (umemo-pathname umemo-category umemo-entry-name)) (dir (file-name-directory path))) (unless (file-directory-p dir) (make-directory dir t)) (write-region (point) (point-max) path) (set-buffer-modified-p nil) (message "Wrote %s" path)))) (defun umemo-electric-return () (interactive) (umemo-fall-back-to-global-key-binding-in-memo-area "\r")) ;;;; define API (defmacro define-usage-memo (command category nth-arg buffer-fmt &optional name-converter-function) "Add usage-memo feature to COMMAND. Define `usage-memo' around-advice for COMMAND. CATEGORY is usage-memo category to use, typically language name. NTH-ARG is COMMAND's argument position (0-origin) representing entry name, NTH-ARG is passed to `ad-get-arg' macro. BUFFER-FMT is a `format' string, which %s is replaced with entry name, representing document display buffer. Optional NAME-CONVERTER-FUNCTION is 1-argument function to convert COMMAND's argument (indicated by NTH-ARG) to entry name. Of course, the default is `identity'. For example: To support `describe-function' (already supported): (define-usage-memo describe-function \"elisp\" 0 \"*Help*\") Because `define-usage-memo' is a macro, COMMAND is not quoted. The CATEGORY is \"elisp\". The NTH-ARG is 0 because `describe-function' takes a symbol to lookup at the first argument. Because NTH-ARG is 0-origin, 0 means the first argument. BUFFER-FMT is same as *Help* buffer: it is only coincidental. Another example: Support RI lookup (Ruby's document-lookup tool) `ri' function is defined in ri-ruby.el. The usage is: (ri ENTRY_NAME) Then `ri' creates ri `ENTRY_NAME' buffer. Instead of reusing a buffer, it creates a buffer per query. That is why BUFFER-FMT uses `format' string! (define-usage-memo ri \"ruby\" 0 \"ri `%s'\") SLIME example: Downcase Lisp's symbol (define-usage-memo slime-describe-function \"cl\" 0 \"*SLIME Description*\" downcase) See also `umemo-initialize' definition. " (let ((converter (or name-converter-function 'identity))) `(defadvice ,command (around usage-memo activate) ad-do-it (let* ((entry-name (funcall ',converter (format "%s" (ad-get-arg ,nth-arg)))) (buf (with-no-warnings (format ,buffer-fmt entry-name)))) (umemo-setup ,category entry-name buf))))) ;; (umemo-initialize) ;; (ri "Array#length") ;; (lh-refe "Array#length") ;; (describe-function 'princ) ;; (slime-documentation "princ") ;; (slime-describe-symbol "princ") ;; (slime-documentation "car") ;;;; sample definition (defun umemo-initialize () "A bunch of `define-usage-memo' definitions. Feel free to redefine!" (define-usage-memo ri "ruby" 0 "ri `%s'") (define-usage-memo lh-refe "ruby" 0 "refe \"%s\"") (define-usage-memo slime-documentation "cl" 0 "*SLIME Description*" downcase) (define-usage-memo slime-describe-symbol "cl" 0 "*SLIME Description*" downcase) (define-usage-memo slime-describe-function "cl" 0 "*SLIME Description*" downcase) (define-usage-memo describe-function "elisp" 0 "*Help*") (define-usage-memo describe-variable "elisp" 0 "*Help*") ) ;;;; minor mode definition (defvar usage-memo-mode-map (make-sparse-keymap)) (define-key usage-memo-mode-map "\C-x\C-s" 'umemo-save) (define-key usage-memo-mode-map "\r" 'umemo-electric-return) (loop for c from ? to ?~ do (define-key usage-memo-mode-map (char-to-string c) 'self-insert-command)) (define-minor-mode usage-memo-mode "Automatically enabled minor mode to add usage-memo feature by `define-usage-memo'. Write your annotation below `===*===*===*===*===*===*===*===*===*===*===' line. \\\\[umemo-save]: Save your annotation to file indicated by `umemo-pathname'. Of course, your annotation is revived even if Emacs is restarted! " nil "" usage-memo-mode-map :global nil (setq buffer-read-only nil) (when view-mode (view-mode -1)) (buffer-enable-undo)) ;;;; SLIME hack (defadvice slime-show-description (after usage-memo-aux activate) "usage-memo hack." (when umemo-current-entry-name (umemo-setup "cl" umemo-current-entry-name "*SLIME Description*"))) ;; (progn (ad-disable-advice 'slime-show-description 'around 'usage-memo) (ad-update 'slime-show-description)) (provide 'usage-memo) ;; How to save (DO NOT REMOVE!!) ;; (let ((oddmuse-wiki "EmacsWiki")(oddmuse-page-name "usage-memo.el")) (call-interactively 'oddmuse-post)) ;;; usage-memo.el ends here