;;; html-helper-mode.el --- Major mode for composing html files.
;; Author: Nelson Minar
;; Maintainer: Nelson Minar
;; Created: 01 Feb 1994
;; Version: $Revision: 1.33 $
;; Keywords: HTML major-mode
;; Copyright (C) 1994 Nelson Minar
;; This program 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 program 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, 675 Mass Ave, Cambridge, MA 02139, USA.
;;; Commentary:
;;{{{
;; Installation:
;; add this line in your .emacs:
;; (autoload 'html-helper-mode "html-helper-mode" "Yay HTML" t)
;; to invoke html-helper-mode automatically on .html files, do this:
;; (setq auto-mode-alist (cons '("\\.html$" . html-helper-mode) auto-mode-alist))
;; Configuration:
;; see the "user variables" section, or the documentation on configuration
;; in http://www.reed.edu/~nelson/tools/. There are variables you want to
;; configure, particularly html-helper-do-write-file-hooks,
;; html-helper-build-new-buffer, and html-helper-address-string
;; Description:
;; html-helper-mode makes it easier to write HTML documents. This mode
;; handles inserting HTML codes in a variety of ways (keybindings,
;; completion in the buffer). It also supports indentation, timestamps,
;; skeletons for new documents, hilit19 patterns, and a variety of other
;; things. For the full skinny, see the HTML documentation that comes
;; with the package or is at http://www.reed.edu/~nelsont/tools/
;; Thank yous:
;; David Kågedal for the tempo code which
;; forms the core of the HTML insertion, as well as the HTML+
;; cookies.
;; Magnus Homann for suggestions and code
;; for the timestamp insertion
;; Marc Andreessen for writing the original html-mode
;; that inspired this one
;; To do:
;; some general way of building menus (easymenu.el)?
;; font-lock patterns
;; The newest version of html-helper-mode is available from
;; http://www.reed.edu/~nelson/tools/
;; ftp://ftp.reed.edu/pub/src/html-helper-mode.tar.Z
;; This code was writting using folding.el, a wonderful folding editor
;; minor mode for emacs. That's what the strange {{{ comments are for.
;;}}}
;;; Code:
;;{{{ user variables
;; features. I recommend you turn these on.
(defvar html-helper-do-write-file-hooks nil
"*If not nil, then html-helper-mode will modify the local-write-file-hooks
to do timestamps.")
(defvar html-helper-build-new-buffer nil
"*If not nil, then html-helper will insert html-helper-new-buffer-strings
when new buffers are generated")
;; (see also tempo.el)
;; variables to configure
(defvar html-helper-basic-offset 2
"*basic indentation size used for list indentation")
(defvar html-helper-item-continue-indent 5
"*Indentation of lines that follow a
item. Default is 5, the length
of things like \"
\" and \"
\".")
(defvar html-helper-never-indent nil
"*If t, the indentation code for html-helper is turned off.")
;; hooks (see also tempo.el)
(defvar html-helper-mode-hook nil
"*Hook run when html-helper-mode is started.")
(defvar html-helper-load-hook nil
"*Hook run when html-helper-mode is loaded.")
(defvar html-helper-timestamp-hook 'html-helper-default-insert-timestamp
"*Hook called for timestamp insertion. Override this for your own
timestamp styles.")
;; strings you might want to change
(defvar html-helper-address-string ""
"*The default author string of each file.")
(defvar html-helper-new-buffer-template
'(" \n"
"" p "\n\n\n"
"\n"
"
" p "
\n\n"
p
"\n\n\n"
"" html-helper-address-string "\n"
html-helper-timestamp-start
html-helper-timestamp-end
"\n \n")
"*Template for new buffers, inserted by html-helper-insert-new-buffer-strings if
html-helper-build-new-buffer is set to t")
(defvar html-helper-timestamp-start "\n"
"*Delimiter for timestamps. Everything between html-helper-timestamp-start
and html-helper-timestamp-end will be deleted and replaced with the output of
the function html-helper-insert-timestamp if html-helper-do-write-file-hooks
is t")
(defvar html-helper-timestamp-end ""
"*Delimiter for timestamps. Everything between html-helper-timestamp-start
and html-helper-timestamp-end will be deleted and replaced with the output of
the function html-helper-insert-timestamp if html-helper-do-write-file-hooks
is t")
;; this is what the byte compiler does to see if its emacs18. You probably
;; don't need to change this.
(defvar html-helper-emacs18
(and (boundp 'emacs-version)
(or (and (boundp 'epoch::version) epoch::version)
(string-lessp emacs-version "19")))
"I'll do minimal emacs18 support, grumble.")
;;}}}
(require 'tempo)
;;{{{ html-helper-mode-syntax-table
;; emacs doesn't really seem to be general enough to handle SGML like
;; syntax. In particular, comments are a loss. We do try this, though:
;; give < and > matching semantics
(defvar html-helper-mode-syntax-table nil
"Syntax table for html-helper.")
(if html-helper-mode-syntax-table
()
(setq html-helper-mode-syntax-table (make-syntax-table text-mode-syntax-table))
(modify-syntax-entry ?< "(> " html-helper-mode-syntax-table)
(modify-syntax-entry ?> ")< " html-helper-mode-syntax-table)
(modify-syntax-entry ?\" ". " html-helper-mode-syntax-table)
(modify-syntax-entry ?\\ ". " html-helper-mode-syntax-table)
(modify-syntax-entry ?' "w " html-helper-mode-syntax-table))
;;}}}
;;{{{ keymap variable and function setup
(defvar html-helper-keymap-list
'(html-helper-head-map html-helper-header-map html-helper-anchor-map
html-helper-logical-map html-helper-phys-map html-helper-list-map
html-helper-note-map html-helper-form-map)
"list of all the subkeymaps html-helper uses")
(defvar html-helper-keymap-alist
'((head . html-helper-head-map)
(header . html-helper-header-map)
(anchor . html-helper-anchor-map)
(logical . html-helper-logical-map)
(phys . html-helper-phys-map)
(list . html-helper-list-map)
(note . html-helper-note-map)
(form . html-helper-form-map))
"alist associating cookie types with keymaps")
;; basic keymap variables (not easy to mapcar a macro)
(defvar html-helper-mode-map (make-sparse-keymap)
"Keymap for html-helper")
(defvar html-helper-head-map nil
"Keymap used for head info.")
(defvar html-helper-header-map nil
"Keymap used for headers.")
(defvar html-helper-anchor-map nil
"Keymap used for anchors.")
(defvar html-helper-logical-map nil
"Keymap used for logical styles.")
(defvar html-helper-phys-map nil
"Keymap used for physical styles.")
(defvar html-helper-list-map nil
"Keymap used for lists.")
(defvar html-helper-note-map nil
"Keymap used for notes.")
(defvar html-helper-form-map nil
"Keymap used for forms.")
;; make keymaps into prefix commands (does this do anything useful in 18?)
(mapcar 'define-prefix-command html-helper-keymap-list)
;; if we're emacs18, we have to build the prefix maps by hand
(if html-helper-emacs18
(mapcar (function (lambda (v) (set v (make-sparse-keymap))))
html-helper-keymap-list))
;; now build the mode keymap.
;; special mode keys
(mapcar
(function (lambda (l) (define-key html-helper-mode-map (car l) (nth 1 l))))
'(("\M-\C-f" tempo-forward-mark)
("\M-\C-b" tempo-backward-mark)
("\M-\t" tempo-complete-tag)
("\M-\C-t" html-helper-insert-timestamp-delimiter-at-point)))
;; indentation keys - only rebind these if the user wants indentation
(if html-helper-never-indent
()
(define-key html-helper-mode-map "\t" 'html-helper-indent-command)
(define-key html-helper-mode-map "\C-m" 'newline-and-indent))
;; special keybindings in the prefix maps (not in the list of cookies)
(define-key html-helper-list-map "i" 'html-helper-smart-insert-item)
;; install the prefix maps themselves into the mode map
;; eval the keymap in 18 so we get the value, not the symbol
(defun html-helper-install-prefix (l)
"Install a prefix key into the map. Special code for emacs18"
(if html-helper-emacs18
(define-key html-helper-mode-map (car l) (eval (nth 1 l)))
(define-key html-helper-mode-map (car l) (nth 1 l))))
(mapcar
'html-helper-install-prefix
'(("\C-c\C-b" html-helper-head-map)
("\C-c\C-t" html-helper-header-map)
("\C-c\C-a" html-helper-anchor-map)
("\C-c\C-s" html-helper-logical-map)
("\C-c\C-p" html-helper-phys-map)
("\C-c\C-l" html-helper-list-map)
("\C-c\C-n" html-helper-note-map)
("\C-c\C-f" html-helper-form-map)))
;;}}}
;;{{{ html-helper-mode-abbrev-table
(defvar html-helper-mode-abbrev-table nil
"Abbrev table used while in html-helper-mode.")
(define-abbrev-table 'html-helper-mode-abbrev-table ())
;;}}}
;;{{{ html-helper-add-cookie function for building basic cookies
(defvar html-helper-tempo-tags nil
"List of tags used in completion.")
(defun html-helper-add-cookie (l)
"Add a new cookie to html-helper-mode. Builds a tempo-template for the
cookie and puts it into the appropriate keymap if a key is
requested."
(let* ((type (car l))
(keymap (cdr-safe (assq type html-helper-keymap-alist)))
(key (nth 1 l))
(tag (nth 2 l))
(name (nth 3 l))
(cookie (nth 4 l))
(doc (nth 5 l))
(command (tempo-define-template name cookie tag doc 'html-helper-tempo-tags)))
(if (stringp key) ;bind at all?
(if keymap ;special keymap?
(define-key (eval keymap) key command) ;bind to prefix
(define-key html-helper-mode-map key command)) ;bind to global
)))
;;}}}
;;{{{ html-helper-smart-insert-item
;; there are two different kinds of items in HTML - those in regular
;; lists
and those in dictionaries
..
;; This command will insert the appropriate one depending on context.
(tempo-define-template "html-item" '(& "
" > (r . "Item: "))
"
"
"Insert a new record in an HTML list"
'html-helper-tempo-tags)
(tempo-define-template "html-definition-item"
'(& "
" > (p . "Term: ")
"\n" "
" > (r . "Definition: "))
"
"
"Insert a new record in an HTML definition list"
'html-helper-tempo-tags)
(defun html-helper-smart-insert-item (&optional arg)
"Insert a new item, either in a regular list or a dictionary."
(interactive "*P")
(let ((case-fold-search t))
(if
(save-excursion
(re-search-backward "
\\|
\\|
\\|\\|
\\|
" nil t)
(looking-at "
\\|
\\|
"))
(tempo-template-html-definition-item arg)
(tempo-template-html-item arg))))
;;}}}
;;{{{ most of the HTML cookies and keymap
;; taken partially from the HTML quick reference and the elements document
;; http://www.ncsa.uiuc.edu/General/Internet/WWW/HTMLQuickRef.html
;; http://info.cern.ch/hypertext/WWW/MarkUp/Tags.html
;; There are also some HTML+ tokens from I don't know which reference
;; I could put documentation in for each command - would that be useful?
(mapcar
'html-helper-add-cookie
'(
;;entities
(entity "\C-c&" "&" "html-ampersand" ("&"))
(entity "\C-c<" "<" "html-less-than" ("<"))
(entity "\C-c>" ">" "html-greater-than" (">"))
(entity "\C-c " " " "html-nonbreaking-space" (" "))
;; logical styles
(logical "p" "
to
;; etc, but that is pretty unwieldy and slow.
(defvar html-helper-any-list-item "
\\|
\\|
")
(defvar html-helper-any-list-start "
\\|
\\|\\|\\|")
(defvar html-helper-any-list-end "
\\|
\\|\\|\\|")
(defvar html-helper-any-list (format "\\(%s\\)\\|\\(%s\\)\\|\\(%s\\)"
html-helper-any-list-item
html-helper-any-list-start
html-helper-any-list-end))
(defvar html-helper-search-limit 2000 "limit on how far back we search")
(defun html-helper-guess-context ()
"figure out what the last list type thing before point is."
(save-excursion
(let* ((lim (max (point-min) (- (point) html-helper-search-limit)))
(context (if (re-search-backward html-helper-any-list lim t)
(cond ((match-beginning 1) 'item)
((match-beginning 2) 'start)
((match-beginning 3) 'end)
(t 'error))
nil)))
(cons context (current-indentation)))))
(defun html-helper-print-context ()
(interactive)
(message "%s" (html-helper-guess-context)))
;;}}}
;;{{{ indentation
(defvar html-helper-print-indent-info nil
"If t, indent will print out information as a message.")
(defun html-helper-indent-command ()
"Command for indenting text. Just calls html-helper-indent."
(interactive)
(html-helper-indent))
;; some of the ideas are borrowed from cc-mode.el.
;; lots of special cases because we're not doing true parsing, we're
;; trying to guess what to do based on what the last item cookie was.
;; this code works best if the cookies that are the beginnings of menus
;; are on the left end of the line, and are already indented.
(defun html-helper-indent ()
"indentation workhorse function."
(if html-helper-never-indent
()
(let ((m (point-marker))
(bol (progn (beginning-of-line) (point))))
;; unindent the line
(delete-region (point) (progn (back-to-indentation) (point)))
(let* ((where (html-helper-guess-context))
(context (car where))
(previ (cdr where))
(newi (cond
((eq context 'end) previ)
((eq context 'item) previ)
((eq context 'start) (+ previ html-helper-basic-offset))
(t previ))))
;; newi is set to the basic indentation, now adjust indentation
;; based on what the current line is.
(if (looking-at html-helper-any-list)
(cond
;; list token and last line was an end?
;; Probably inside a continued item - go backwards.
((and (match-beginning 1) (eq context 'end))
(setq newi (- newi html-helper-item-continue-indent)))
;; end of list and last line was an end?
;; Probably inside a continued item - go backwards twice
((and (match-beginning 3) (eq context 'end))
(setq newi (- newi html-helper-item-continue-indent html-helper-basic-offset)))
;; Any other end of list?
;; Indent negative
((match-beginning 3)
(setq newi (- newi html-helper-basic-offset)))
;; start of list and last line
;; Beginning of continued item - go forwards
((and (match-beginning 2) (eq context 'item))
(setq newi (+ newi html-helper-item-continue-indent))))
;; we're not any sort of item, must be text.
(cond
;; last line an item?
;; Beginning of continued item - go forward
((eq context 'item)
(setq newi (+ newi html-helper-item-continue-indent)))))
(if html-helper-print-indent-info
(message "Context: %s, Previous: %s New: %s" context previ newi))
;; just in case
(if (< newi 0)
(setq newi 0))
(indent-to newi newi)
;; adjust point to where it was before, or at start of indentation
(goto-char (marker-position m))
(if (< (current-column) (current-indentation))
(back-to-indentation))))))
;;}}}
;;{{{ timestamps
(defun html-helper-update-timestamp ()
"Basic function for updating timestamps. It finds the timestamp in
the buffer by looking for html-helper-timestamp-start, deletes all text
up to html-helper-timestamp-end, and runs html-helper-timestamp-hook
which will presumably insert an appropriate timestamp in the buffer."
(save-excursion
(goto-char (point-max))
(if (not (search-backward html-helper-timestamp-start nil t))
(message "timestamp delimiter start was not found")
(let ((ts-start (+ (point) (length html-helper-timestamp-start)))
(ts-end (if (search-forward html-helper-timestamp-end nil t)
(- (point) (length html-helper-timestamp-end))
nil)))
(if (not ts-end)
(message "timestamp delimiter end was not found. Type C-c C-t to insert one.")
(delete-region ts-start ts-end)
(goto-char ts-start)
(run-hooks 'html-helper-timestamp-hook)))))
nil)
(defun html-helper-default-insert-timestamp ()
"Default timestamp insertion function"
(insert "Last modified: "
(current-time-string)
"\n"))
(defun html-helper-insert-timestamp-delimiter-at-point ()
"Simple function that inserts timestamp delimiters at point, useful
for adding timestamps to existing buffers."
(interactive)
(insert html-helper-timestamp-start)
(insert html-helper-timestamp-end))
;;}}}
;;{{{ html-helper-insert-new-buffer-strings
(tempo-define-template "html-skeleton" html-helper-new-buffer-template
nil
"Insert a skeleton for a HTML document")
(defun html-helper-insert-new-buffer-strings ()
"Insert html-helper-new-buffer-strings."
(tempo-template-html-skeleton))
;;}}}
;;{{{ html-helper-mode
(defun html-helper-mode ()
"
Mode for editing HTML documents. For more documentation and the newest
version, see http://www.reed.edu/~nelson/tools/
The main function html-helper-mode provides is a bunch of keybindings
for the HTML cookies one inserts when writing HTML documents. Typing
the key sequence for a command inserts the corresponding cookie and
places point in the right place. If a prefix argument is supplied, the
cookie is instead wrapped around the region.
There is also code for indentation, timestamps, skeletons for new
documents, and lots of other neat features.
\\{html-helper-mode-map}
Written by nelson@reed.edu, http://www.reed.edu/~nelson/
"
(interactive)
(kill-all-local-variables)
(use-local-map html-helper-mode-map)
(setq local-abbrev-table html-helper-mode-abbrev-table)
(set-syntax-table html-helper-mode-syntax-table)
(setq mode-name "HTML")
(setq major-mode 'html-helper-mode)
(make-local-variable 'comment-start)
(make-local-variable 'comment-end)
(make-local-variable 'comment-column)
(make-local-variable 'comment-start-skip)
(make-local-variable 'indent-line-function)
(setq comment-start ""
comment-start-skip "" comment)
("" define)
("" nil define)
("" include)
("" "" bold)
("" "" italic)
("" "" underline)
("&" ";" string)
("<" ">" keyword))
nil 'case-insensitive)
nil)
;;}}}
(provide 'html-helper-mode)
(run-hooks 'html-helper-load-hook)
;;; html-helper-mode.el ends here