;;; steno.el --- edit time-stamped entries in text notepads under Emacs.
;;; $Revision: 1.1 $

;; Copyright (C) 1996 David Megginson

;; Author: David Megginson (dmeggins@uottawa.ca)

;; steno.el 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.

;; steno.el 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 or XEmacs; see the file COPYING.  If not,
;; write to the Free Software Foundation, Inc., 59 Temple Place -
;; Suite 330, Boston, MA 02111-1307, USA.


;;; Commentary:

;; This package has some similarities to Emacs' Changelog support,
;; but it is designed for a different purpose.  It allows users
;; to keep a collection of simple logs or diaries ("steno pads")
;; in a single directory, and to add new entries to them quickly.
;;
;; To install this package, copy steno.el to a directory on your
;; load-path and optionally compile it (it is not speed-critical),
;; then add the following to your .emacs file, site-start.el, or any
;; other relevant place:
;;
;; (autoload 'steno "steno" nil t)
;; (autoload 'steno-view "steno" nil t)
;;
;; You can bind either or both of these to menus or to key strokes.
;; If you wanted to bind 'steno to "\C-cs", for example, you could
;; use the command
;;
;; (global-set-key "\C-cs" 'steno)
;;
;; You may also want to change the value of the 'steno-pad-directory
;; variable.  By default it is set to $HOME/.steno, which is fine for
;; Unix users, but DOS/Windows users might prefer a different default.
;; The 'steno or 'steno-view functions will offer to create the
;; directory if it doesn't already exist.
;;
;; Use "M-x steno-view" to open a steno pad without starting a new
;; entry (though you can always do so later), or "M-x steno" to start
;; a new entry right away.  Inside a steno pad, the most important key
;; strokes are "\C-c\C-a" to add a new time-stamped entry, "\C-c\C-n"
;; to move to the next entry, "\C-c\C-p" to move to the previous
;; entry, and "\C-c\C-c" to save the steno pad and remove its window
;; from the display.  'steno-mode is derived from 'text-mode, so any key
;; sequences defined in 'text-mode-map will be available in
;; 'steno-mode as well.

;; I may add menu support and multi-file searching in a future release.


;;; Code:

(require 'derived)

;;
;; Variable for specifying the location of the steno directory.
;;
(defvar steno-pad-directory (expand-file-name "~/.steno")
  "Variable to specify the location of the steno-pad directory.
The default is good for Unix, but should be changed for Windows/DOS.")

;;
;; Regular expression for recognising entries.
;;
(defvar steno-entry-regexp "^\\*\\* [SMTWF][a-z]+.*[0-9][0-9][0-9][0-9]$"
  "Regular expression for recognising entries.")

;;
;; Regular expression for hilighting.
;;
(defvar steno-font-lock-keywords
  (list
   (list steno-entry-regexp 0 'font-lock-keyword-face)
   (list "^-\\*-Steno-\\*-.*$" 0 'font-lock-comment-face)))

;;
;; Create a new major mode for Steno pads.
;;
(define-derived-mode steno-mode text-mode "Steno"
  "Major mode for editing or viewing steno pads.
Steno pads are simple text files, kept in a common directory, with
dated entries (similar to a change log).

\\{steno-mode-map}"

  (make-local-variable 'font-lock-defaults)
  (setq font-lock-defaults '(steno-font-lock-keywords t))
  (define-key steno-mode-map "\C-c\C-n" 'steno-next-entry)
  (define-key steno-mode-map "\C-c\C-p" 'steno-previous-entry)
  (define-key steno-mode-map "\C-c\C-a" 'steno-add-entry)
  (define-key steno-mode-map "\C-c\C-c" 'steno-close)
  (run-hooks 'steno-mode-hook))

;;
;; Show a steno pad and start a new entry.
;;
(defun steno (pad)
  "Show a steno pad and start a new entry.
If called interactively, the user will be prompted for the pad name.
For more information, see steno-mode."
  (interactive (steno-choose-pad))
  (steno-view pad)
  (steno-add-entry))

;;
;; Show a steno pad, but don't start a new entry.
;;
(defun steno-view (pad)
  "Show a steno pad and start a new entry.
If called interactively, the user will be prompted for the pad name.
For more information, see steno-mode."
  (interactive (steno-choose-pad))
  (let ((buffer (find-file-noselect (concat steno-pad-directory
					    "/"
					    pad)
				    t)))
    (set-buffer buffer)
    (steno-mode)
    (switch-to-buffer-other-window buffer)
    (if (not (file-exists-p (buffer-file-name)))
	(insert (format "-*-Steno-*- (%s)\n\n" pad)))))

;;
;; Prompt the user for a steno pad.
;;
(defun steno-choose-pad ()
					; Does the directory exist?
  (if (file-exists-p steno-pad-directory)

					; Is it readable?
      (cond ((not (file-readable-p steno-pad-directory))
	     (error "steno-pad-directory \"%s\" is not readable."
		    steno-pad-directory))
					; Is it a directory?
	    ((not (car (file-attributes steno-pad-directory)))
	     (error "steno-pad-directory \"%s\" is not a directory.")))

					; If it doesn't exist, can
					; we create it?
    (if (y-or-n-p (format "Create directory \"%s\"? " steno-pad-directory))
	(make-directory steno-pad-directory t)
      (error "steno-pad-directory \"%s\" does not exist.")))

					; Read the pad from the user.
  (let ((pads (directory-files steno-pad-directory))
	(pad nil))

    (setq pad (completing-read "Select a steno pad: "
			       (mapcar 'list pads)))

					; If it is new, can we create it?
    (if (not (file-exists-p (concat steno-pad-directory
				    "/"
				    pad)))
	(if (not (y-or-n-p (format "Create new pad \"%s\"? " pad)))
	    (error "Pad \"%s\" does not exist.")))

					; Return the full path.
    (list pad)))

;;
;; Display a list of steno files.  Allow user to select a file
;; and edit it.  Attention: very simple minded code!
;;
(defun steno-dired ()
  "Invoke dired on the steno directory.
Allow user to select steno pads and edit them.
Very simple minded code!"
  (interactive)
  (dired (concat steno-pad-directory "/*[!~]"))
  (rename-buffer "*Steno-List*"))

;;
;; Add an entry to a steno pad.
;;    
(defun steno-add-entry ()
  "Add a time-stamped entry to the end of a steno pad.
See steno-mode for more information."
  (interactive)
  (goto-char (point-max))
  (cond ((search-backward-regexp "[^ \t\n\r\f]" nil t)
	 (end-of-line)
	 (delete-region (point) (point-max))
	 (insert (format "\n\n\n** %s\n\n" (current-time-string))))
	(t
	 (goto-char (point-min))
	 (delete-region (point) (point-max))
	 (insert (format "** %s\n\n" (current-time-string))))))

;;
;; Move forward one entry in a steno pad.
;;
(defun steno-next-entry (n)
  "Move forward to the n entries in a steno pad.
If called interactively, will move the number of entries
specified by the prefix argument."
  (interactive "p")
  (while (> n 0)
    (let ((old (point)))
      (beginning-of-line)
      (forward-char 1)
      (if (search-forward-regexp steno-entry-regexp
				 nil t)
	  (beginning-of-line)
	(progn
	  (goto-char old)
	  (error "No more entries in steno pad.")))
      (setq n (1- n)))))

;;
;; Move backwards one entry in a steno pad.
;;
(defun steno-previous-entry (n)
  "Move backwards n entries in a steno pad.
If called interactively, will move the number of entries
specified by the prefix argument."
  (interactive "p")
  (while (> n 0)
    (let ((old (point)))
      (if (search-backward-regexp "^\*\* [SMTWF][a-z]+.*[0-9][0-9][0-9][0-9]$"
				 nil t)
	  (beginning-of-line)
	(progn
	  (goto-char old)
	  (error "No more entries in steno pad.")))
      (setq n (1- n)))))

;;
;; Finish with a steno pad -- save to disk and hide it.
;;
(defun steno-close ()
  "Finish with a steno pad -- save it to disk and kill the buffer."
  (interactive)
  (save-buffer)
  (let ((buf (current-buffer)))
    (delete-windows-on buf)
    (kill-buffer buf)))
  

;;
;; For (require 'steno)
;;
(provide 'steno)
