;;; html-helper-mode.el --- Major mode for composing html files. ;; Author: Nelson Minar ;; Maintainer: Nelson Minar ;; Created: 01 Feb 1994 ;; Version: $Revision: 2.0 $ ;; Keywords: HTML major-mode ;; LCD Archive Entry: ;; html-helper-mode|Nelson Minar|nelson@reed.edu| ;; Major mode for editing HTML.| ;; 16-Mar-94|Version 2.0|ftp://ftp.reed.edu/pub/src/html-helper-mode.tar.Z ;; 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)) ;; NOTE - this mode requires another lisp file, tempo.el. This can be ;; retrieved from ftp://ftp.lysator.liu.se/pub/emacs/tempo.el ;; 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/~nelson/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 ;; a way to send the right "load URL" signal to xmosaic for the current file? ;; The newest version of html-helper-mode should always be 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 html-helper-image-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) (image . html-helper-image-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.") (defvar html-helper-image-map nil "Keymap used for images.") ;; 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) ("\C-c\C-i" html-helper-image-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. (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
          " >)) (list "l" "
        • " "html-item" (& "
        • " > (r . "Item: "))) (list "t" "
          " "html-definition-item" (& "
          " > (p . "Term: ") "\n
          " > (r . "Definition: "))) ;;anchors (anchor "n" "" (r . "Anchor text: ") "")) (anchor "l" "" (r . "Anchor text: ") "")) ;;graphics (image "i" "")) (image "t" "	")) (image "a" nil "html-align-image" ("")) (image "e" "\""")) ;;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)))))) ;;}}} ;;{{{ completion finder for tempo (defvar html-helper-completion-finder (if html-helper-emacs18 'html-helper-emacs18-completion-finder "\\(\\(<\\|&\\).*\\)\\=") "Passed to tempo-use-tag-list, used to find tags to complete.") ;; The regexp finds everything between the last < or & and point, ;; which is good enough to match the tags HTML might complete. ;; emacs18 doesn't have the \= for regexps, though, so we do something ;; more hackish. (defun html-helper-emacs18-completion-finder () "Unfortunately emacs18 doesn't support \\= in regexps, so we do this hack. If you have problems with it, maybe you should upgrade to emacs19 :-)" (let* ((where nil) (s (buffer-substring (point) (setq where (save-excursion (re-search-backward "<\\|&" (min (point-min) 100) t) (point)))))) (cons s where))) ;;}}} ;;{{{ 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. Alternately, one can type in part of the cookie and complete it. 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 helper") (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) ;;}}} ;;{{{ ;; This part has been added from an older version of html-helper-mode ;; to support the CSS module. ;; Begin CSS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; html-helper-mode has a concept of "type" of tags. Each type is a ;; list of tags that all go together in one keymap and one menu. ;; Types can be added to the system after html-helper has been loaded, ;; briefly by doing html-helper-add-type-to-alist, then ;; html-helper-install-type, then html-helper-add-tag (for each tag) ;; then html-helper-rebuild-menu. See the mode documentation for more detail. (defconst html-helper-type-alist nil "Alist: type of tag -> keymap, keybinding, menu, menu string. Add to this with `html-helper-add-type-to-alist'.") ;;{{{ accessor functions for html-helper-type-alist (defun html-helper-keymap-for (type) "Accessor function for alist: for type, return keymap or nil" (nth 0 (cdr-safe (assq type html-helper-type-alist)))) (defun html-helper-key-for (type) "Accessor function for alist: for type, return keybinding or nil" (nth 1 (cdr-safe (assq type html-helper-type-alist)))) (defun html-helper-menu-for (type) "Accessor function for alist: for type, return menu or nil" (nth 2 (cdr-safe (assq type html-helper-type-alist)))) (defun html-helper-menu-string-for (type) "Accessor function for alist: for type, return menustring or nil" (nth 3 (cdr-safe (assq type html-helper-type-alist)))) (defun html-helper-normalized-menu-for (type) "Helper function for building menus from submenus: add on string to menu." (cons (html-helper-menu-string-for type) (eval (html-helper-menu-for type)))) ;;}}} (defun html-helper-add-type-to-alist (type) "Add a type specification to the alist. The spec goes (type . (keymap-symbol keyprefix menu-symbol menu-string)). See code for an example." (setq html-helper-type-alist (cons type html-helper-type-alist))) ;; Once html-helper-mode is aware of a type, it can then install the ;; type: arrange for keybindings, menus, etc. (defconst html-helper-installed-types nil "The types that have been installed (used when building menus). There is no support for removing a type once it has been installed.") (defun html-helper-install-type (type) "Install a new tag type: add it to the keymap, menu structures, etc. For this to work, the type must first have been added to the list of types with html-helper-add-type-to-alist." (setq html-helper-installed-types (cons type html-helper-installed-types)) (let ((keymap (html-helper-keymap-for type)) (key (html-helper-key-for type)) (menu (html-helper-menu-for type)) (menu-string (html-helper-menu-string-for type))) (and key (progn (set keymap nil) (define-prefix-command keymap) (if html-helper-emacs18 (progn (set keymap (make-sparse-keymap)) (define-key html-helper-mode-map key (eval keymap))) (define-key html-helper-mode-map key keymap)))) (and menu (progn (set menu nil))))) ;;{{{ html-helper-add-tag function for building basic tags (defvar html-helper-tempo-tags nil "List of tags used in completion.") ;; this while loop is awfully Cish ;; isn't there an emacs lisp function to do this? (defun html-helper-string-to-symbol (input-string) "Given a string, downcase it and replace spaces with -. We use this to turn menu entries into good symbols for functions. It's not entirely successful, but fortunately emacs lisp is forgiving." (let* ((s (copy-sequence input-string)) (l (1- (length s)))) (while (> l 0) (if (char-equal (aref s l) ?\ ) (aset s l ?\-)) (setq l (1- l))) (concat "html-" (downcase s)))) (defun html-helper-add-tag (l) "Add a new tag to html-helper-mode. Builds a tempo-template for the tag and puts it into the appropriate keymap if a key is requested. Format: `(html-helper-add-tag '(type keybinding completion-tag menu-name template doc)'" (let* ((type (car l)) (keymap (html-helper-keymap-for type)) (menu (html-helper-menu-for type)) (key (nth 1 l)) (completer (nth 2 l)) (name (nth 3 l)) (tag (nth 4 l)) (doc (nth 5 l)) (command (tempo-define-template (html-helper-string-to-symbol name) tag completer doc 'html-helper-tempo-tags))) (if (null (memq type html-helper-installed-types)) ;type loaded? t ;no, do nothing. (if (stringp key) ;bind key somewhere? (if keymap ;special keymap? (define-key (eval keymap) key command) ;t: bind to prefix (define-key html-helper-mode-map key command));nil: bind to global t) (if menu ;is there a menu? (set menu ;good, cons it in (cons (vector name command t) (eval menu)))) ))) ;; for backwards compatability (fset 'html-helper-add-cookie 'html-helper-add-tag) ;;}}} ;; control over what types of tags to load. By default, we load all the ;; ones we know of. (defvar html-helper-types-to-install '(anchor list header logical phys textel java entity colors image head form) "*List of tag types to install when html-helper-mode is first loaded. If you want to not install some type of tag, override this variable. Order is significant: menus go in this order.") ;; install the default types. (mapcar 'html-helper-install-type html-helper-types-to-install) ;; 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))) ;; Features; these are all good to have on. (see also tempo.el) (defvar html-helper-use-expert-menu t "*If not nil, then use the full HTML menu.") (defvar html-helper-do-write-file-hooks t "*If not nil, then modify `local-write-file-hooks' to do timestamps.") (defvar html-helper-build-new-buffer t "*If not nil, then insert `html-helper-new-buffer-strings' for new buffers.") (defvar html-helper-user-menu nil "*Extra items to put in the HTML expert menu. The value of this symbol is appended to the beginning of the expert menu that is handed off to easymenu for definition. It should be a list of vectors or lists which themselves are vectors (for submenus).") ;;{{{ menu support ;; menus are built for easymenu. html-helper-add-tag builds ;; submenus based on tag type, the expert menu code lumps them ;; together into one list and calls easy-menu-define (defvar html-helper-novice-menu '("HTML" ["Insert Paragraph" tempo-template-html-paragraph t] ["Insert Hyperlink" tempo-template-html-hyperlink t] ["Insert Big Header" tempo-template-html-header-2 t] ["Insert Unordered List" tempo-template-html-unordered-list t] ["Insert List Item" html-helper-smart-insert-item t] ["Insert Inlined Image" tempo-template-html-image-with-alternate-text t] ["Turn on Expert Menu" html-helper-toggle-expert-menu t]) "Menu for novices, only installed if `html-helper-use-expert-menu is nil'") (defun html-helper-menu nil "Return the proper menu. Looks at `html-helper-use-expert-menu'" (if html-helper-use-expert-menu (html-helper-expert-menu) html-helper-novice-menu)) (defun html-helper-rebuild-menu nil "Rebuild and install the HTML menu (using `easy-menu-define'). If `html-helper-use-expert-menu' is nil, then just use a novice menu." (let ((menu (html-helper-menu))) ; (easy-menu-remove menu) (easy-menu-define html-helper-mode-menu-symbol html-helper-mode-map "HTML menus" menu) (easy-menu-add menu html-helper-mode-map))) (defun html-helper-toggle-expert-menu (&optional arg) "Toggle full HTML menus. Optional arg acts like minor-mode args." (interactive "P") (setq html-helper-use-expert-menu (if (null arg) (not html-helper-use-expert-menu) (> (prefix-numeric-value arg) 0))) (html-helper-rebuild-menu)) ;; If browse-url loaded, add this in the novice menu. (if (fboundp 'browse-url-of-file) (setq html-helper-novice-menu (append html-helper-novice-menu (list ["Load This Buffer in Browser" browse-url-of-file t])))) ;; Expert menus: consed up out of html-helper-installed-types (defun html-helper-expert-menu () "This menu is based on the current value of `html-helper-installed-types'. This function can be called again, it redoes the entire menu." ;; first, reset this so we can call this again and again. (setq html-helper-mode-menu nil) ;; Cons in the toggle of the menu (setq html-helper-mode-menu (cons '["Turn on Novice Menu" html-helper-toggle-expert-menu t] html-helper-mode-menu)) ;; Now add in user-provided menu stuff (setq html-helper-mode-menu (append html-helper-user-menu html-helper-mode-menu)) ;; Now cons in the browse-url functions (if (fboundp 'browse-url-of-file) (setq html-helper-mode-menu (cons '["Load this Buffer in Browser" browse-url-of-file t] html-helper-mode-menu))) (if (and (boundp 'browse-url-browser-function) (fboundp browse-url-browser-function)) (setq html-helper-mode-menu (cons (vector "Browse URL at point" browse-url-browser-function t) html-helper-mode-menu))) ;; cons in the timestamp delimiters (setq html-helper-mode-menu (cons '["Insert Timestamp Delimiter" html-helper-insert-timestamp-delimiter-at-point t] html-helper-mode-menu)) ;; now cons up the main menu out of the submenus (mapcar (function (lambda (type) (setq html-helper-mode-menu (cons (html-helper-normalized-menu-for type) html-helper-mode-menu)))) html-helper-installed-types) ;; now tack on our name (setq html-helper-mode-menu (cons "HTML" html-helper-mode-menu)) html-helper-mode-menu) (html-helper-rebuild-menu) ;;}}} ;; End CSS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide 'html-helper-mode) (run-hooks 'html-helper-load-hook) ;;; html-helper-mode.el ends here