;;; 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" "
        "		"html-preformatted"   	  ("
        " (r . "Text: ") "
        ")) (logical "b" "
        " "html-blockquote" ("
        " (r . "Quote: ") "
        ")) (logical "e" "" "html-emphasized" ("" (r . "Text: ") "")) (logical "s" "" "html-strong" ("" (r . "Text: ") "")) (logical "c" "" "html-code" ("" (r . "Code: ") "")) (logical "x" "" "html-sample" ("" (r . "Sample code") "")) (logical "r" "" "html-citation" ("" (r . "Citation: ") "")) (logical "k" "" "html-keyboard" ("" (r . "Keyboard: ") "")) (logical "v" "" "html-variable" ("" (r . "Variable: ") "")) (logical "d" "" "html-definition" ("" (r . "Definition: ") "")) (logical "q" "" "html-quote" ("" (r . "Quote: ") "")) (logical "n" "" "html-person" ("" (r . "Person: ") "")) (logical "y" "" "html-acronym" ("" (r . "Acronym: ") "")) (logical "." "" "html-abbrev" ("" (r . "Abbrevation: ") "")) (logical "m" "" "html-cmd" ("" (r . "Command name: ") "")) (logical "g" "" "html-arg" ("" (r . "Argument: ") "")) (logical "l" "" "html-lit" ("" r "")) (logical "a" "
        " "html-address" ("
        " r "
        ")) ;;physical styles (phys "b" "" "html-bold" ("" (r . "Text: ") "")) (phys "i" "" "html-italic" ("" (r . "Text: ") "")) (phys "u" "" "html-underline" ("" (r . "Text: ") "")) (phys "f" "" "html-fixed" ("" (r . "Text: ") "")) (phys "x" "" "html-strikethru" ("" (r . "Text: ") "")) (phys "^" "" "html-superscript" ("" (r . "Text: ") "")) (phys "_" "" "html-subscript" ("" (r . "Text: ") "")) (phys "r" "")) ;;headers (header "1" "

        " "html-header-1" ("

        " (r . "Header: ") "

        ")) (header "2" "

        " "html-header-2" ("

        " (r . "Header: ") "

        ")) (header "3" "

        " "html-header-3" ("

        " (r . "Header: ") "

        ")) (header "4" "

        " "html-header-4" ("

        " (r . "Header: ") "

        ")) (header "5" "
        " "html-header-5" ("
        " (r . "Header: ") "
        ")) (header "6" "
        " "html-header-6" ("
        " (r . "Header: ") "
        ")) (note "a" "" "html-abstract" ("\n" r "\n\n")) (note "n" "" (r . "Note text: ") "")) (note "f" "" "html-footnote" ("" (r . "Footnote: ") "")) (note "m" "" "html-margin" ("" (r . "Margin note: ") "")) ;; forms (form "f" "\n" r "\n\n")) (form "t" "")) (form "i" "")) (form "." "")) (form "d" "")) (form "u" "")) (form "c" "")) (form "r" "")) (form "g" "")) (form "s" "")) (form "a" "")) (form "b" "")) (form "x" "")) (form "p" "\n" r "\n\n")) (form "c" "\n" r "\n\n\n")"" "html-ordered-list" (& "
          " > "\n
        1. " > (r . "Item: ") "\n
        " >)) (list "u" "
          " "html-unordered-list" (& "
            " > "\n
          • " > (r . "Item: ") "\n
          " >)) (list "r" "" "html-directory" (& "" > "\n
        • " > (r . "Item: ") "\n
        • " >)) (list "m" "" "html-menu" (& "" > "\n
        • " > (r . "Item: ") "\n
        • " >)) (list "d" "
          " "html-definition-list" (& "
          " > "\n
          " > (p . "Term: ") "\n
          " > (r . "Definition: ") "\n
          " >)) ;;anchors (anchor "n" "" (r . "Anchor text: ") "")) (anchor "l" "" (r . "Anchor text: ") "")) ;;graphics (graphic "\C-c\C-i" "")) ;;text elements (textel "\e\C-m" nil "html-paragraph" ("

          \n")) (textel "\C-c-" nil "html-horizontal-rule" (& "


          \n")) (textel "\C-c\C-m" nil "html-break" ("
          \n")) ;;head elements (head "t" "" "html-title" ("<title>" (r . "Document title: ") "")) (head "i" "" "html-isindex" ("\n")) (head "n" "" "html-nextid" ("\n")) (head "l" "")) (head "b" "")) )) ;;}}} ;;{{{ context guessing ;; guess where we are in indented lists based on the last list token. ;; it would be much better to try to match
        to
          , and
      to
        ;; etc, but that is pretty unwieldy and slow. (defvar html-helper-any-list-item "
      1. \\|
        \\|
        ") (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