diff lisp/modes/ksh-mode.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children ac2d302a0011
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/modes/ksh-mode.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,1234 @@
+;; ksh-mode.el --- sh (ksh, bash) script editing mode for GNU Emacs.
+
+;; Copyright (C) 1992-95 Gary Ellison.
+
+;; This file is part of XEmacs.
+
+;; XEmacs 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.
+
+;; XEmacs 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 XEmacs; see the file COPYING.  If not, write to the Free
+;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;; LCD Archive Entry:
+;; ksh-mode|Gary F. Ellison|Gary_F_Ellison@ATT.COM
+;; Mode for editing sh/ksh/bash scripts
+;; 23-Feb-95|2.6|~/modes/ksh-mode.el.Z|
+
+;; Author: Gary F. Ellison <Gary.F.Ellison@ATT.COM>
+;;                   AT&T Bell Laboratories
+;;                   6200 East Broad Street
+;;                   Columbus, Ohio 43213 USA
+;;
+;; Maintainer: Gary F. Ellison <Gary.F.Ellison@ATT.COM>
+;; Created: Fri Jun 19
+;; Version: 2.6
+;; Keywords: languages, shell, korn, bourne, sh, ksh, bash, unix
+;;
+;; Delta On        : 2/23/95
+;; Last Modified By: Gary Ellison
+;; Last Modified On: Thu Feb 23 11:32:03 1995
+;; Update Count    : 33
+;; Status          : Highly Functional
+;;
+
+;;; Commentary:
+
+;;
+;; Description:
+;;   sh, ksh, and bash script editing commands for emacs.
+;; 
+;; Installation:
+;;   Put ksh-mode.el in some directory in your load-path.
+;;   Refer to the installation section of ksh-mode's function definition.
+;;
+;; Usage:
+;;   This major mode assists shell script writers with indentation
+;;   control and control structure construct matching in much the same
+;;   fashion as other programming language modes. Invoke describe-mode
+;;   for more information.
+;; 
+;; Bugs:
+;;   When the ksh-align-to-keyword is non-nil and the nester
+;;   is a multi-command expression with a compound command
+;;   the lines following the compound end will align incorrectly
+;;   to the compound command instead of it's current indentation.
+;;   The fix will probably require the detection of syntax elements
+;;   in the nesting line.
+;;   
+;;   Function ending brace "}" must be on a separate line for indent-line
+;;   to do the right thing.
+;;
+;;   Explicit function definition matching will proclaim in the minibuffer
+;;   "No matching compound command" followed by "Matched ... "
+;;
+;;   indent-for-comment fails to recognize a comment starting in column 0,
+;;   hence it moves the comment-start in comment-column.
+
+;;; Code:
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; HISTORY 
+;; 8-Aug-95		Jack Repenning <jackr@sgi.com>
+;;    Fix documentation of `ksh-align-to-keyword' to conform to the 23
+;;    Feb default change.  Search for keywords obeying case, since the
+;;    shell does.
+;;
+;; 23-Feb-1995		Gary Ellison	
+;;    Merged Jonathan Stigelman <Stig@hackvan.com> into 2.5 souce.
+;;
+;; 23 Feb 1995          Jonathan Stigelman <Stig@hackvan.com>
+;;    Reshuffled documentation to make the format more consistant with other
+;;    elisp.  Added autoload and removed autoloading instructions from the
+;;    ksh-mode docstring.  Changed default value for `ksh-align-to-keyword'
+;;    to nil because it doesn't work properly.
+;;
+;; 2-Aug-1994		Gary Ellison	
+;;    Last Modified: Mon Jun 13 16:52:55 1994 #29 (Gary Ellison)
+;;    - Syntax table modifications to better support sexp navigation and
+;;      parsing.
+;;    - Fixed keyword regexps. Keywords were not being recoginized on the
+;;      same line as " ' `.
+;;
+;; 13-Jun-1994		Gary Ellison	
+;;    Last Modified: Wed Mar 30 14:12:26 1994 #28 (Gary Ellison)
+;;    - Minor excursion problem fixed in ksh-indent-command.
+;;
+;; 30-Mar-1994		Gary Ellison	
+;;    Last Modified: Fri Mar 25 15:42:29 1994 #25 (Gary Ellison)
+;;    - Implement user customizable ksh-comment-regexp.
+;;    - Make the keyword vs line indentation alignment customizable
+;;      by calling ksh-align-to-keyword based on variable of same
+;;      name. (If the code is obfuscated or convoluted I can attribute
+;;      this to a severe head cold and not malice :)
+;;
+;; 25-Mar-1994		Gary Ellison	
+;;    Last Modified: Fri Feb  4 13:06:30 1994 #23 (Gary Ellison)
+;;    - Nest relative to the line indentation not the keywords
+;;      column.
+;;
+;; 4-Feb-1994		Gary Ellison	
+;;    Last Modified: Wed Nov 10 10:03:01 1993 #18 (Gary Ellison)
+;;    - Add direct support for font-lock-mode. Thanks Espen Skoglund
+;;      for the regular expressions.
+;;
+;; 10-Nov-1993		Gary Ellison	
+;;    Last Modified: Tue Oct 12 15:23:06 1993 #17 (Gary Ellison)
+;;    Fix message on ksh-match-and-tell to not get invalid format
+;;    when a % appears in the string.
+;;
+;; 12-Oct-1993		Espen Skoglund <espensk@stud.cs.uit.no>.
+;;    Last Modified: Tue Oct 12 15:03:01 1993 #16 (Gary Ellison)
+;;    Apply Line continuation patch supplied by Espen Skoglund
+;;
+;; 1-Sep-1993		Gary Ellison	
+;;    Last Modified: Tue Aug 17 17:18:18 1993 #14 (Gary Ellison)
+;;    Get rid of this-line hack in ksh-get-nester-column.
+;;
+;; 17-Aug-1993		Gary Ellison	
+;;    Last Modified: Mon Jun 21 14:00:43 1993 #13 (Gary Ellison)
+;;    Code uses builtin current-indentation instead of lisp defun
+;;    ksh-indentation-on-this-line (thanks to Tom Tromey).
+;;    More and better doc strings.
+;;
+;; 5-Aug-1993		Tom Tromey <tromey@cns.caltech.edu>
+;;    Last Modified: Thu Aug  5 11:09:12 1993 #12 (Tom Tromey)
+;;    ksh-indent-region skips blank lines.  Uses let binding instead
+;;    of setq.  No longer marks buffer modified if indentation
+;;    doesn't change. 
+;;
+;; 21-Jun-1993		Gary Ellison	
+;;    Last Modified: Mon Mar 29 15:05:34 1993 #11 (Gary Ellison)
+;;    Use make-local-variables instead of make-variables-buffer-local
+;;    ksh-indent now supports nil (keyword aligned) or number (offset)
+;;    Support ksh-tab-always-indent feature
+;;    Variables offsetting indentation renamed to better reflect their
+;;    role.
+;;    Integrate keyword completion feature supplied by
+;;    Haavard Rue <hrue@imf.unit.no>.
+;;
+;; 29-Mar-1993		Gary Ellison	
+;;    Last Modified: Tue Sep 29 16:14:02 1992 #10 (Gary Ellison)
+;;    Integrate line continuation patch supplied by
+;;    Haavard Rue <hrue@imf.unit.no>
+;;    Name back to ksh-mode to avoid confusion with sh-mode
+;;    by Thomas W. Strong, Jr. <strong+@cmu.edu>.
+;;
+;; 29-Sep-1992		Gary Ellison	
+;;    Last Modified: Wed Sep  2 08:51:40 1992 #9 (Gary Ellison)
+;;    Full support of ksh88 case items. 
+;;    Align statements under "do" and "then" keywords one position 
+;;    past the keyword.
+;;
+;; 2-Sep-1992		Gary Ellison	
+;;    Last Modified: Tue Aug  4 14:34:35 1992 #8 (Gary Ellison)
+;;    Use make-variable-buffer-local instead of make-local-variable
+;;    Get rid of superflous ksh-default variables.
+;;    Use end of word match \b for "then", "do", "else", "elif"
+;;    Support process substitution lists and exclude ksh 88 case items
+;;    Use default-tab-width for indentation defaults.
+;;    Moved installation instructions to the mode level documentation 
+;;    section.
+;;    Fixed auto-mode-alist documentation.
+;;
+;; 24-Jul-1992		Gary Ellison	
+;;    Last Modified: Fri Jul 24 09:45:11 1992 #7 (Gary Ellison)
+;;    Modified ksh-indent-region to use marker versus fixed end point.
+;;    comment-start-skip regexp no longer fooled by parameter substitution.
+;;    Added constant ksh-mode-version.
+;;
+;; 21-Jul-1992		Gary Ellison	
+;;    Last Modified: Tue Jul 21 15:53:57 1992 #6 (Gary Ellison)
+;;    Indent with tabs instead of spaces.
+;;    Can handle just about all styles.
+;;    Anti-newline in REs.
+;;    Word delim "\b" in REs
+;;    More syntax entries.
+;;    Variables with regexp suffix abbreviated to re
+;;    Better } handling
+;;    Implemented minimal indent-region-function
+;;    Mode documentation corrected.
+;;    Minor lisp source format changes.
+;;    
+;; 29-Jun-1992		Gary Ellison	
+;;    Last Modified: Mon Jun 29 15:39:35 1992 #5 (Gary Ellison)
+;;    Optimize line-to-string
+;;    Implicit/Explicit functions aok
+;;    More indentation variables
+;;    Superfluous defun killed.
+;;    renamed to sh-mode
+;;    
+;; 22-Jun-1992          Gary Ellison
+;;    Last Modified: Mon Jun 22 15:01:14 1992 #4 (Gary Ellison)
+;;    Cleanup pre att.emacs posting
+;;
+;; 19-Jun-1992          Gary Ellison
+;;    Last Modified: Fri Jun 19 17:19:14 1992 #3 (Gary Ellison)
+;;    Minimal case indent handling
+;;
+;; 19-Jun-1992          Gary Ellison
+;;    Last Modified: Fri Jun 19 16:23:26 1992 #2 (Gary Ellison)
+;;    Nesting handled except for case statement
+;;
+;; 19-Jun-1992          Gary Ellison
+;;    Last Modified: Fri Jun 19 10:03:07 1992 #1 (Gary Ellison)
+;;    Conception of this mode.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defconst ksh-mode-version "2.6"
+  "*Version numbers of this version of ksh-mode")
+
+;;
+;; Variables controlling indentation style
+;;
+
+(defvar ksh-indent 2 
+  ;; perhaps c-basic-offset would be okay to use as a default, but using
+  ;; default-tab-width as the default is ridiculous --Stig
+  "*Indentation of ksh statements with respect to containing block. A value
+of nil indicates compound list keyword \(\"do\" and \"then\"\) alignment.")
+(defvar ksh-case-item-offset ksh-indent
+  "*Additional indentation for case items within a case statement.")
+(defvar ksh-case-indent nil
+  "*Additional indentation for statements under case items.")
+(defvar ksh-group-offset (- ksh-indent)
+  "*Additional indentation for keywords \"do\" and \"then\".")
+(defvar ksh-brace-offset 0
+  "*Additional indentation of \"{\" under functions or brace groupings.")
+(defvar ksh-multiline-offset 1
+  "*Additional indentation of line that is preceded of a line ending with a
+\\ to make it continue on next line.")
+(defvar ksh-match-and-tell t
+  "*If non-nil echo in the minibuffer the matching compound command
+for the \"done\", \"}\", \"fi\", or \"esac\". ")
+(defvar ksh-tab-always-indent t
+  "*Controls the operation of the TAB key. If t (the default), always
+reindent the current line.  If nil, indent the current line only if
+point is at the left margin or in the line's indentation; otherwise
+insert a tab.")
+
+(defvar ksh-align-to-keyword nil
+  ;; #### - this is broken, so it should be disabled by default --Stig
+  "*Controls whether nested constructs align from the keyword or
+the current indentation. If non-nil, indentation will be relative to
+the column the keyword starts. If nil, indentation will be relative to
+the current indentation of the line the keyword is on.
+The default value is nil.
+The non-nil case doesn't work very well.")
+
+(defvar ksh-comment-regexp "^\\s *#"
+  "*Regular expression used to recognize comments. Customize to support
+ksh-like languages.")
+
+(defun ksh-current-indentation ()
+  nil
+  )
+;;
+(fset 'ksh-current-indentation 'current-column)
+;;
+;; Variables controlling completion
+(defvar ksh-completion-list '())
+(make-variable-buffer-local 'ksh-completion-list)
+(set-default 'ksh-completion-list  '())
+
+;;
+;; -type-  : type number, 0:misc, 1:variable, 2:function
+;; -regexp-: regexp used to parse the script
+;; -match- : used by match-beginning/end to pickup target
+;;
+(defvar ksh-completion-type-misc 0)
+(defvar ksh-completion-regexp-var "\\([A-Za-z_0-9]+\\)=")
+(defvar ksh-completion-type-var 1)
+(defvar ksh-completion-match-var 1) 
+(defvar ksh-completion-regexp-var2 "\\$\\({\\|{#\\)?\\([A-Za-z_0-9]+\\)[#%:}]?")
+(defvar ksh-completion-match-var2 2)
+(defvar ksh-completion-regexp-function
+  "\\(function\\)?[ \t]*\\([A-Za-z_0-9]+\\)[ \t]*([ \t]*)")
+(defvar ksh-completion-type-function 2)
+(defvar ksh-completion-match-function 2)
+
+;;
+;; Variable controlling fontification
+;;
+(defvar ksh-keywords '("for" "in" "do" "done" "select" "case" "esac" "if"
+"then" "elif" "else" "fi" "while" "until" "function" "time"
+"alias" "bg" "break" "continue" "cd" "echo" "fc" "fg" "getopts" "jobs" "kill"
+"let" "newgrp" "print" "pwd" "read" "readonly" "return" "set" "shift" "test"
+"times" "trap" "typeset" "ulimit" "umask" "unalias" "unset" "wait" "whence"))
+
+;;       '("\\<function[ \t]+\\([^(; \t]+\\)" 1 font-lock-function-name-face)
+(defconst ksh-font-lock-keywords
+      (list
+       ;; Fontify [[ ]] expressions
+       '("\\(\\[.*\\]\\)"  1 font-lock-doc-string-face t)
+       ;; Fontify keywords
+       (cons (concat
+	      "\\(\\<"
+	      (mapconcat 'identity ksh-keywords "\\>\\|\\<")
+	      "\\>\\)")
+	     1)
+       ;; Fontify function names
+       '("\\<function[ \t]+\\([^(; \t]+\\)" 1 font-lock-function-name-face)
+       '("\\(^[ \t]*[A-Za-z_][A-Za-z_0-9]*[ \t]*()\\)" 1 font-lock-function-name-face)
+       ))
+
+(put 'ksh-mode	'font-lock-keywords 'ksh-font-lock-keywords)
+
+;; XEmacs change -- This can incorrectly set some Perl scripts to
+;; ksh-mode.  It also won't work for some other shells which ksh-mode
+;; nominally works with.
+;(defun ksh-check-hook ()
+;    (save-excursion
+;     (save-restriction
+;       (widen)
+;       (goto-char (point-min))
+;       (cond ((looking-at "#![ \t]*/.*/k?sh[ \t]*")
+;	      (ksh-mode))))))
+;
+;(add-hook 'find-file-hooks 'ksh-check-hook)
+
+;;
+;; Context/indentation regular expressions
+;; 
+;; indenting expressions
+;;
+(defconst ksh-then-do-re     "^[^#\n]*\\s\"*\\b\\(then\\|do\\)\\b"
+  "*Regexp used to locate grouping keywords: \"then\" and \"do\"" )
+
+;;(defconst ksh-do-re          "^[ \t]*\\bdo\\(\\b\\|$\\)"
+(defconst ksh-do-re          "^\\s *\\bdo\\(\\b\\|$\\)"
+  "*Regexp used to match keyword: do")
+
+(defconst ksh-then-re        "^\\s *\\bthen\\(\\b\\|$\\)"
+  "*Regexp used to match keyword: then")
+
+;;
+;; Structure starting/indenting keywords
+;;
+(defconst ksh-else-re           "^\\s *\\belse\\(\\b\\|$\\)"
+  "*Regexp used to match keyword: else")
+
+(defconst ksh-elif-re           "^\\s *\\belif\\(\\b\\|$\\)"
+  "*Regexp used to match keyword: elif")
+
+(defconst ksh-brace-re           "^\\S>*{[ \t\n]"
+  "*Regexp used to match syntactic entity: { ")
+
+(defconst ksh-case-item-end-re           "^\\S>*;;[ \t\n]"
+  "*Regexp used to match case item end syntactic entity: ;;")
+
+(defconst ksh-keywords-re
+  "^[^#\n]*\\s\"*\\b\\(else\\|if\\|elif\\|case\\|while\\|for\\|until\\|select\\)\\b"
+  "*Regexp used to detect compound command keywords: if, else, elif case, 
+while, for, until, and select")
+
+
+(defconst ksh-if-re         "^[^#\n]*\\s\"*\\b\\(if\\)\\b"
+  "*Regexp used to match keyword: if")
+
+(defconst ksh-iteration-keywords-re 
+  "^[^#\n]*\\s\"*\\b\\(while\\|for\\|until\\|select\\)\\b"
+  "*Match one of the keywords: while, until, for, select")
+
+(defconst ksh-case-re           "^[^#\n]*\\s\"*\\b\\(case\\)\\b"
+  "*Regexp used to match keyword: case")
+
+(defconst ksh-explicit-func-re
+  "^\\s *\\(function\\s [a-zA-z_][a-zA-Z0-1_]*\\)\\b"
+  "*Match an explicit function definition: function name")
+
+(defconst ksh-implicit-func-re
+  "^\\s *\\([a-zA-z_][a-zA-Z0-1_]*\\)\\s *()\\s *"
+  "*Match an implicit function definition: name ()")
+
+(defconst ksh-func-brace-re "^\\s *\\(.*{\\)[ \t\n]+"
+  "*Match a implicit function definition brace: name { ")
+
+;;
+;; indenting 
+(defconst ksh-case-item-re           "^[^#\n]*\\s\"*\\()\\)"
+  "*Regexp used to match case-items including ksh88")
+
+(defconst ksh-paren-re           "^[^#\n]*\\s\"*)[ \t\n]+"
+  "*Regexp used to match compound list & case items")
+
+;;
+;; structure ending keyword regular expressions
+(defconst ksh-fi-re            "^\\s *fi\\b"
+  "*Regexp used to match keyword: fi")
+
+(defconst ksh-esac-re          "^\\s *esac\\b"
+  "*Regexp used to match keyword: esac")
+
+(defconst ksh-done-re          "^\\s *done\\b"
+  "*Regexp used to match keyword: done")
+
+(defconst ksh-brace-end-re  "^\\s *}\\s *"
+  "*Regexp used to match function brace-groups")
+
+(defconst ksh-multiline-re "^.*\\\\$"
+  "*Regexp used to match a line with a statement using more lines.")
+
+;;
+;;
+;; Create mode specific tables
+(defvar ksh-mode-syntax-table nil
+  "Syntax table used while in ksh mode.")
+(if ksh-mode-syntax-table
+    ()
+  (setq ksh-mode-syntax-table (make-syntax-table))
+  (modify-syntax-entry ?\' "\"" ksh-mode-syntax-table)
+  (modify-syntax-entry ?` "\"" ksh-mode-syntax-table)
+  (modify-syntax-entry ?\n ">" ksh-mode-syntax-table)
+  (modify-syntax-entry ?\f ">" ksh-mode-syntax-table)
+  (modify-syntax-entry ?# "<" ksh-mode-syntax-table)
+  (modify-syntax-entry ?_ "w" ksh-mode-syntax-table)
+  (modify-syntax-entry ?< "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?> "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?& "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?| "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?$ "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?% "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?= "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?/ "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?+ "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?* "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?- "." ksh-mode-syntax-table)
+  (modify-syntax-entry ?\; "." ksh-mode-syntax-table)
+  )
+
+(defvar ksh-mode-abbrev-table nil
+  "Abbrev table used while in ksh mode.")
+(define-abbrev-table 'ksh-mode-abbrev-table ())
+
+(defvar ksh-mode-map nil 
+  "Keymap used in ksh mode")
+
+(if ksh-mode-map
+    ()
+  (setq ksh-mode-map (make-sparse-keymap))
+  (define-key ksh-mode-map "\t"    'ksh-indent-command)
+;;  (define-key ksh-mode-map "\n"    'reindent-then-newline-and-indent)
+;;  (define-key ksh-mode-map '[return] 'reindent-then-newline-and-indent)
+;;  (define-key ksh-mode-map "\t"    'ksh-indent-line)
+;;  (define-key ksh-mode-map "\177"    'backward-delete-char-untabify)
+  (define-key ksh-mode-map "\C-j"    'reindent-then-newline-and-indent)
+  (define-key ksh-mode-map "\e\t"    'ksh-complete-symbol)
+  (define-key ksh-mode-map "\C-c\t"    'ksh-completion-init-and-pickup)
+  )
+
+
+;;;###autoload
+(defun ksh-mode ()
+  "ksh-mode 2.6 - Major mode for editing (Bourne, Korn or Bourne again)
+shell scripts.
+Special key bindings and commands:
+\\{ksh-mode-map}
+Variables controlling indentation style:
+ksh-indent
+    Indentation of ksh statements with respect to containing block.
+    Default value is 2.
+ksh-case-indent
+    Additional indentation for statements under case items.
+    Default value is nil which will align the statements one position 
+    past the \")\" of the pattern.
+ksh-case-item-offset
+    Additional indentation for case items within a case statement.
+    Default value is 2.
+ksh-group-offset
+    Additional indentation for keywords \"do\" and \"then\".
+    Default value is -2.
+ksh-brace-offset
+    Additional indentation of \"{\" under functions or brace groupings.
+    Default value is 0.
+ksh-multiline-offset
+   Additional indentation of line that is preceded of a line ending with a
+   \\ to make it continue on next line.
+ksh-tab-always-indent
+    Controls the operation of the TAB key. If t (the default), always
+    reindent the current line.  If nil, indent the current line only if
+    point is at the left margin or in the line's indentation; otherwise
+    insert a tab.
+ksh-match-and-tell
+    If non-nil echo in the minibuffer the matching compound command
+    for the \"done\", \"}\", \"fi\", or \"esac\". Default value is t.
+
+ksh-align-to-keyword
+    Controls whether nested constructs align from the keyword or
+    the current indentation. If non-nil, indentation will be relative to
+    the column the keyword starts. If nil, indentation will be relative to
+    the current indentation of the line the keyword is on.
+    The default value is non-nil.
+
+ksh-comment-regexp
+  Regular expression used to recognize comments. Customize to support
+  ksh-like languages. Default value is \"\^\\\\s *#\".
+
+Style Guide.
+ By setting
+    (setq ksh-indent default-tab-width)
+    (setq ksh-group-offset 0)
+
+    The following style is obtained:
+
+    if [ -z $foo ]
+	    then
+		    bar    # <-- ksh-group-offset is additive to ksh-indent
+		    foo
+    fi
+
+ By setting
+    (setq ksh-indent default-tab-width)
+    (setq ksh-group-offset (- 0 ksh-indent))
+
+    The following style is obtained:
+
+    if [ -z $foo ]
+    then
+	    bar
+	    foo
+    fi
+
+ By setting
+    (setq ksh-case-item-offset 1)
+    (setq ksh-case-indent nil)
+
+    The following style is obtained:
+
+    case x in *
+     foo) bar           # <-- ksh-case-item-offset
+          baz;;         # <-- ksh-case-indent aligns with \")\"
+     foobar) foo
+             bar;;
+    esac
+
+ By setting
+    (setq ksh-case-item-offset 1)
+    (setq ksh-case-indent 6)
+
+    The following style is obtained:
+
+    case x in *
+     foo) bar           # <-- ksh-case-item-offset
+           baz;;        # <-- ksh-case-indent
+     foobar) foo
+           bar;;
+    esac
+    
+
+Installation:
+  Put ksh-mode.el in some directory in your load-path.
+  Put the following forms in your .emacs file.
+
+ (setq auto-mode-alist
+      (append auto-mode-alist
+              (list
+               '(\"\\\\.sh$\" . ksh-mode)
+               '(\"\\\\.ksh$\" . ksh-mode)
+               '(\"\\\\.bashrc\" . ksh-mode)
+               '(\"\\\\..*profile\" . ksh-mode))))
+
+ (setq ksh-mode-hook
+      (function (lambda ()
+         (font-lock-mode 1)             ;; font-lock the buffer
+         (setq ksh-indent 8)
+	 (setq ksh-group-offset -8))
+	 (setq ksh-brace-offset -8)   
+         (setq ksh-tab-always-indent t)
+         (setq ksh-match-and-tell t)
+         (setq ksh-align-to-keyword t)	;; Turn on keyword alignment
+	 )))"
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map ksh-mode-map)
+  (setq major-mode 'ksh-mode)
+  (setq mode-name "Ksh")
+  (setq local-abbrev-table ksh-mode-abbrev-table)
+  (set-syntax-table ksh-mode-syntax-table)
+  (make-local-variable 'indent-line-function)
+  (setq indent-line-function 'ksh-indent-line)
+  (make-local-variable 'indent-region-function)
+  (setq indent-region-function 'ksh-indent-region)
+  (make-local-variable 'comment-start)
+  (setq comment-start "# ")
+  (make-local-variable 'comment-end)
+  (setq comment-end "")
+  (make-local-variable 'comment-column)
+  (setq comment-column 32)
+  (make-local-variable 'comment-start-skip)
+  (setq comment-start-skip "#+ *")
+  ;;
+  ;; config font-lock mode
+  (make-local-variable 'font-lock-keywords) 
+  (setq font-lock-keywords ksh-font-lock-keywords)
+  ;;
+  ;; Let the user customize
+  (run-hooks 'ksh-mode-hook)
+  (if (not ksh-align-to-keyword)
+      (ksh-align-to-keyword -1)
+    )
+  ) ;; defun
+
+;;
+;; Support functions
+
+(defun ksh-align-to-keyword (&optional arg)
+  "Toggle value of ksh-align-to-keyword and rebind the ksh-current-indentation
+function. With arg, force alignment to keyword if and only if arg is positive."
+  (interactive)
+  (if (null arg)			;just toggle
+      (cond ((not ksh-align-to-keyword)
+	     (setq ksh-align-to-keyword t)
+	     (fset 'ksh-current-indentation 'current-column))
+	    (t
+	     (setq ksh-align-to-keyword nil)
+	     (fset 'ksh-current-indentation 'current-indentation))
+	    )
+    (cond ((natnump arg)
+	   (setq ksh-align-to-keyword t)
+	   (fset 'ksh-current-indentation 'current-column))
+	  (t
+	   (setq ksh-align-to-keyword nil)
+	   (fset 'ksh-current-indentation 'current-indentation))
+	  ))
+  )
+
+(defun ksh-current-line ()
+  "Return the vertical position of point in the buffer.
+Top line is 1."
+  (+ (count-lines (point-min) (point))
+     (if (= (current-column) 0) 1 0))
+  )
+
+
+(defun ksh-line-to-string ()
+  "From point, construct a string from all characters on
+current line"
+  (skip-chars-forward " \t") ;; skip tabs as well as spaces
+  (buffer-substring (point)
+                    (progn
+                      (end-of-line 1)
+                      (point))))
+
+(defun ksh-get-nest-level ()
+  "Return a 2 element list (nest-level nest-line) describing where the
+current line should nest."
+  (let ((case-fold-search)
+    	(level))
+    (save-excursion
+      (forward-line -1)
+      (while (and (not (bobp))
+		  (null level))
+	(if (and (not (looking-at "^\\s *$"))
+ 		 (not (save-excursion
+ 			(forward-line -1)
+ 			(beginning-of-line)
+			(looking-at ksh-multiline-re)))
+		 (not (looking-at ksh-comment-regexp)))
+	    (setq level (cons (current-indentation)
+			      (ksh-current-line)))
+	  (forward-line -1)
+	  );; if
+	);; while
+      (if (null level)
+	  (cons (current-indentation) (ksh-current-line))
+	level)
+      )
+    )
+  )
+
+(defun ksh-looking-at-compound-list ()
+  "Return true if current line contains compound list initiating keyword"
+  (or 
+   (looking-at ksh-do-re)
+   (looking-at ksh-then-re)
+   ) ;; or
+  ) ;; defun
+
+(defun ksh-looking-at-case-item ()
+  "Return true if current line is a case-item .vs. paren compound list"
+  (save-excursion
+    (beginning-of-line)
+    ;;
+    ;; Handle paren indentation constructs for this line
+    (cond ((looking-at ksh-paren-re)
+	   (goto-line (cdr (ksh-get-nest-level)))
+	   ;;
+	   ;; The question is whether this is really a case item or just
+	   ;; parenthesized compound list.
+	   (cond ((or (looking-at ksh-case-re)
+		      (looking-at ksh-case-item-end-re)))
+		 ;;
+		 ;; turns out to be a parenthesized compound list
+		 ;; so propigate the nil for cond
+		 )
+	   ))
+    )
+  ) ;; defun
+
+(defun ksh-get-case-indent ()
+  "Return the column of the closest open case statement"
+  (save-excursion
+    (let (
+	  (nest-list (ksh-get-compound-level ksh-case-re ksh-esac-re (point)))
+	  )
+      (if (null nest-list)
+	  (progn 
+	    (if ksh-match-and-tell
+		(message "No matching case for ;;"))
+	    0)
+	(car nest-list)))
+    )
+  )
+
+;;
+;; Functions which make this mode what it is
+;;
+
+(defun ksh-get-nester-column (nest-line)
+  "Return the column to indent to with respect to nest-line taking 
+into consideration keywords and other nesting constructs."
+  (save-excursion 
+    (let ((fence-post)
+	  (nester-column)
+	  (case-fold-search)
+	  (start-line (ksh-current-line)))
+      ;;
+      ;; Handle case item indentation constructs for this line
+      (cond ((ksh-looking-at-case-item)
+	     (save-excursion
+	       (goto-line nest-line)
+	       (let ((fence-post (save-excursion (end-of-line) (point))))
+		 ;;
+		 ;; Now know there is a case-item so detect whether
+		 ;; it is first under case, just another case-item, or
+		 ;; a case-item and case-item-end all rolled together.
+		 ;;
+		 (cond ((re-search-forward ksh-case-re fence-post t)
+			(goto-char (match-beginning 1))
+			(+ (ksh-current-indentation) ksh-case-item-offset))
+
+		       ((ksh-looking-at-case-item)
+			(current-indentation))
+
+		       ((looking-at ksh-case-item-end-re)
+			(end-of-line)
+			(+ (ksh-get-case-indent) ksh-case-item-offset))
+		       )
+		 )))
+	    (t;; Not a case-item.  What to do relative to the nest-line?
+	     (save-excursion
+	       (goto-line nest-line)
+	       (setq fence-post (save-excursion (end-of-line) (point)))
+	       (setq nester-column
+		     (save-excursion
+		       (cond
+			;;
+			;; Check if we are in a continued statement
+			((and (looking-at ksh-multiline-re)
+			      (save-excursion
+				(goto-line (1- start-line))
+				(looking-at ksh-multiline-re)))
+			 (+ (current-indentation) ksh-multiline-offset))
+
+			;; In order to locate the column of the keyword,
+			;; which might be embedded within a case-item,
+			;; it is necessary to use re-search-forward.
+			;; Search by literal case, since shell is
+			;; case-sensitive.
+			((re-search-forward ksh-keywords-re fence-post t)
+			 (goto-char (match-beginning 1))
+			 (if (looking-at ksh-case-re)
+			     (+ (ksh-current-indentation) ksh-case-item-offset)
+			   (+ (ksh-current-indentation)
+			      (if (null ksh-indent)
+				  2 ksh-indent)
+			      )))
+
+			((re-search-forward ksh-then-do-re fence-post t)
+			 (if (null ksh-indent)
+			     (progn 
+			       (goto-char (match-end 1))
+			       (+ (ksh-current-indentation) 1))
+			   (progn
+			     (goto-char (match-beginning 1))
+			     (+ (ksh-current-indentation) ksh-indent))
+			   ))
+
+			((looking-at ksh-brace-re)
+			 (+ (current-indentation)
+			    (if (null ksh-indent)
+				2 ksh-indent)
+			    ))
+			;;
+			;; Forces functions to first column
+			((or (looking-at ksh-implicit-func-re)
+			     (looking-at ksh-explicit-func-re))
+			 (if (looking-at ksh-func-brace-re)
+			     (if (null ksh-indent)
+				 2 ksh-indent)
+			   ksh-brace-offset))
+
+			;;
+			;; Need to first detect the end of a case-item
+			((looking-at ksh-case-item-end-re)
+			 (end-of-line)
+			 (+ (ksh-get-case-indent) ksh-case-item-offset))
+			;;
+			;; Now detect first statement under a case item
+			((ksh-looking-at-case-item)
+			 (if (null ksh-case-indent)
+			     (progn
+			       (re-search-forward ksh-case-item-re fence-post t)
+			       (goto-char (match-end 1))
+			       (+ (current-column) 1))
+			   (+ (current-indentation) ksh-case-indent)))
+
+			;; This is hosed when using current-column
+			;; and there is a multi-command expression as the
+			;; nester.
+			(t (current-indentation)))
+		       )
+		     ));; excursion over
+	     ;;
+	     ;; Handle additional indentation constructs for this line
+	     (cond ((ksh-looking-at-compound-list)
+		    (+ nester-column ksh-group-offset))
+		   ((looking-at ksh-brace-re)
+		    (+ nester-column ksh-brace-offset))
+		   (t nester-column))
+	     );; Not a case-item
+	    )
+      );;let
+    );; excursion
+  );; defun
+
+(defun ksh-indent-command ()
+  "Indent current line relative to containing block and allow for
+ksh-tab-always-indent customization"
+  (interactive)
+  (let (case-fold-search)
+    (cond ((save-excursion
+	     (skip-chars-backward " \t")
+	     (bolp))
+	   (ksh-indent-line))
+	  (ksh-tab-always-indent
+	   (save-excursion
+	     (ksh-indent-line)))
+	  (t (insert-tab))
+	  ))
+  )
+
+
+(defun ksh-indent-line ()
+  "Indent current line as far as it should go according
+to the syntax/context"
+  (interactive)
+  (let (case-fold-search)
+    (save-excursion
+      (beginning-of-line)
+      (if (bobp)
+	  nil
+	;;
+	;; Align this line to current nesting level
+	(let*
+	    (
+	     (level-list (ksh-get-nest-level)) ; Where to nest against
+	     ;;           (last-line-level (car level-list))
+	     (this-line-level (current-indentation))
+	     (nester-column (ksh-get-nester-column (cdr level-list)))
+	     (struct-match (ksh-match-structure-and-reindent))
+	     )
+	  (if struct-match
+	      (setq nester-column struct-match))
+	  (if (eq nester-column this-line-level)
+	      nil
+	    (beginning-of-line)
+	    (let ((beg (point)))
+	      (back-to-indentation)
+	      (delete-region beg (point)))
+	    (indent-to nester-column))
+	  );; let*
+	);; if
+      );; excursion
+    ;;
+    ;; Position point on this line
+    (let*
+	(
+	 (this-line-level (current-indentation))
+	 (this-bol (save-excursion
+		     (beginning-of-line)
+		     (point)))
+	 (this-point (- (point) this-bol))
+	 )
+      (cond ((> this-line-level this-point);; point in initial white space
+	     (back-to-indentation))
+	    (t nil)
+	    );; cond
+      );; let*
+    );; let
+  );; defun
+
+
+(defun ksh-match-indent-level (begin-re end-re)
+  "Match the compound command and indent. Return nil on no match,
+indentation to use for this line otherwise."
+  (interactive)
+  (let* ((case-fold-search)
+	 (nest-list 
+	  (save-excursion
+	    (ksh-get-compound-level begin-re end-re (point))
+	    ))
+	 ) ;; bindings
+    (if (null nest-list)
+	(progn
+	  (if ksh-match-and-tell
+	      (message "No matching compound command"))
+	  nil) ;; Propagate a miss.
+      (let* (
+	     (nest-level (car nest-list))
+	     (match-line (cdr nest-list))
+	     ) ;; bindings
+	(if ksh-match-and-tell
+	    (save-excursion
+	      (goto-line match-line)
+	      (message "Matched ... %s" (ksh-line-to-string))
+	      ) ;; excursion
+	  ) ;; if ksh-match-and-tell
+	nest-level ;;Propagate a hit.
+	) ;; let*
+      ) ;; if
+    ) ;; let*
+  ) ;; defun ksh-match-indent-level
+
+(defun ksh-match-structure-and-reindent ()
+  "If the current line matches one of the indenting keywords
+or one of the control structure ending keywords then reindent. Also
+if ksh-match-and-tell is non-nil the matching structure will echo in
+the minibuffer"
+  (interactive)
+  (let (case-fold-search)
+    (save-excursion
+      (beginning-of-line)
+      (cond ((looking-at ksh-else-re)
+	     (ksh-match-indent-level ksh-if-re ksh-fi-re))
+	    ((looking-at ksh-elif-re)
+	     (ksh-match-indent-level ksh-if-re ksh-fi-re))
+	    ((looking-at ksh-fi-re)
+	     (ksh-match-indent-level ksh-if-re ksh-fi-re))
+	    ((looking-at ksh-done-re)
+	     (ksh-match-indent-level ksh-iteration-keywords-re ksh-done-re))
+	    ((looking-at ksh-esac-re)
+	     (ksh-match-indent-level ksh-case-re ksh-esac-re))
+	    ;;
+	    ((looking-at ksh-brace-end-re)
+	     (cond
+	      ((ksh-match-indent-level ksh-implicit-func-re ksh-brace-end-re))
+	      ((ksh-match-indent-level ksh-explicit-func-re ksh-brace-end-re))
+	      ((ksh-match-indent-level ksh-func-brace-re ksh-brace-end-re))
+	      (t nil)))
+	    (t nil)
+	    );; cond
+      )
+    ))
+
+(defun ksh-get-compound-level 
+  (begin-re end-re anchor-point &optional balance-list)
+  "Determine how much to indent this structure. Return a list (level line) 
+of the matching compound command or nil if no match found."
+  (let* 
+      (;; Locate the next compound begin keyword bounded by point-min
+       (match-point (if (re-search-backward begin-re (point-min) t)
+			(match-beginning 1) 0))
+       (nest-column (if (zerop match-point)
+			1 
+		      (progn
+			(goto-char match-point)
+			(ksh-current-indentation))))
+       (nest-list (cons 0 0))    ;; sentinel cons since cdr is >= 1
+       )
+    (if (zerop match-point)
+	nil ;; graceful exit from recursion
+      (progn
+	(if (nlistp balance-list)
+	    (setq balance-list (list)))
+	;; Now search forward from matching start keyword for end keyword
+	(while (and (consp nest-list) (zerop (cdr nest-list))
+		    (re-search-forward end-re anchor-point t))
+	  (if (not (memq (point) balance-list))
+	      (progn
+		(setq balance-list (cons (point) balance-list))
+		(goto-char match-point)  ;; beginning of compound cmd
+		(setq nest-list
+		      (ksh-get-compound-level begin-re end-re
+					     anchor-point balance-list))
+		)))
+
+	(cond ((consp nest-list)
+	       (if (zerop (cdr nest-list))
+		 (progn
+		   (goto-char match-point)
+		   (cons nest-column (ksh-current-line)))
+		 nest-list))
+	      (t nil)
+	      )
+	)
+      )
+    )
+  )
+
+
+(defun ksh-indent-region (start end)
+  "From start to end, indent each line."
+  ;; The algorithm is just moving through the region line by line with
+  ;; the match noise turned off.  Only modifies nonempty lines.
+  (save-excursion
+    (let (ksh-match-and-tell
+	  (endmark (copy-marker end)))
+      
+      (goto-char start)
+      (beginning-of-line)
+      (setq start (point))
+      (while (> (marker-position endmark) start)
+	(if (not (and (bolp) (eolp)))
+	    (ksh-indent-line))
+	(forward-line 1)
+	(setq start (point)))
+
+      (set-marker endmark nil)
+      )
+    )
+  )
+
+;;
+;; Completion code supplied by Haavard Rue <hrue@imf.unit.no>.
+;;
+;;
+;; add a completion with a given type to the list
+;;
+(defun ksh-addto-alist (completion type)
+  (setq ksh-completion-list
+	(append ksh-completion-list
+		(list (cons completion type)))))
+;;
+;; init the list and pickup all 
+;;
+(defun ksh-completion-init-and-pickup ()
+  (interactive)
+  (let (case-fold-search)
+    (ksh-completion-list-init)
+    (ksh-pickup-all)))
+
+;;
+;; init the list
+;;
+(defun ksh-completion-list-init ()
+  (interactive)
+  (setq ksh-completion-list
+	(list
+	 (cons "if"  ksh-completion-type-misc)
+	 (cons "while"  ksh-completion-type-misc)
+	 (cons "until"  ksh-completion-type-misc)
+	 (cons "select"  ksh-completion-type-misc)
+	 (cons "for"  ksh-completion-type-misc)
+	 (cons "continue"  ksh-completion-type-misc)
+	 (cons "function"  ksh-completion-type-misc)
+	 (cons "fi"  ksh-completion-type-misc)
+	 (cons "case"  ksh-completion-type-misc)
+	 (cons "esac"  ksh-completion-type-misc)
+	 (cons "break"  ksh-completion-type-misc)
+	 (cons "exit"  ksh-completion-type-misc)
+	 (cons "done"  ksh-completion-type-misc)
+	 (cons "do"  ksh-completion-type-misc))))
+
+(defun ksh-eol-point ()
+  (save-excursion
+    (end-of-line)
+    (point)))
+
+(defun ksh-bol-point ()
+  (save-excursion
+    (beginning-of-line)
+    (point)))
+
+(defun ksh-pickup-all ()
+  "Pickup all completions in buffer."
+  (interactive)
+  (ksh-pickup-completion-driver (point-min) (point-max) t))
+
+(defun ksh-pickup-this-line ()
+  "Pickup all completions in current line."
+  (interactive)
+  (ksh-pickup-completion-driver (ksh-bol-point) (ksh-eol-point) nil))
+
+(defun ksh-pickup-completion-driver (pmin pmax message)
+  "Driver routine for ksh-pickup-completion."
+  (if message
+      (message "pickup completion..."))
+  (let* (
+	 (i1
+	  (ksh-pickup-completion  ksh-completion-regexp-var
+				 ksh-completion-type-var
+				 ksh-completion-match-var
+				 pmin pmax))
+	 (i2
+	  (ksh-pickup-completion  ksh-completion-regexp-var2
+				 ksh-completion-type-var
+				 ksh-completion-match-var2
+				 pmin pmax))
+	 (i3
+	  (ksh-pickup-completion  ksh-completion-regexp-function
+				 ksh-completion-type-function
+				 ksh-completion-match-function
+				 pmin pmax)))
+    (if message
+	(message "pickup %d variables and %d functions." (+ i1 i2) i3))))
+
+(defun ksh-pickup-completion (regexp type match pmin pmax)
+  "Pickup completion in region and addit to the list, if not already
+there." 
+  (let ((i 0) kw obj)
+    (save-excursion
+      (goto-char pmin)
+      (while (and
+	      (re-search-forward regexp pmax t)
+	      (match-beginning match)
+	      (setq kw  (buffer-substring
+			 (match-beginning match)
+			 (match-end match))))
+	(progn
+	  (setq obj (assoc kw ksh-completion-list))
+	  (if (or (equal nil obj)
+		  (and (not (equal nil obj))
+		       (not (= type (cdr obj)))))
+	      (progn
+		(setq i (1+ i))
+		(ksh-addto-alist kw type))))))
+    i))
+
+(defun ksh-complete-symbol ()
+  "Perform completion."
+  (interactive)
+  (let* ((case-fold-search)
+	 (end (point))
+         (beg (unwind-protect
+                  (save-excursion
+                    (backward-sexp 1)
+                    (while (= (char-syntax (following-char)) ?\')
+                      (forward-char 1))
+                    (point))))
+         (pattern (buffer-substring beg end))
+	 (predicate 
+	  ;;
+	  ;; ` or $( mark a function
+	  ;;
+	  (save-excursion
+	    (goto-char beg)
+	    (if (or
+		 (save-excursion
+		   (backward-char 1)
+		   (looking-at "`"))
+		 (save-excursion
+		   (backward-char 2)
+		   (looking-at "\\$(")))
+		(function (lambda (sym)
+			    (equal (cdr sym) ksh-completion-type-function)))
+	      ;;
+	      ;; a $, ${ or ${# mark a variable
+	      ;;
+	      (if (or
+		   (save-excursion
+		     (backward-char 1)
+		     (looking-at "\\$"))
+		   (save-excursion
+		     (backward-char 2)
+		     (looking-at "\\${"))
+		   (save-excursion
+		     (backward-char 3)
+		     (looking-at "\\${#")))
+		  (function (lambda (sym)
+			      (equal (cdr sym)
+				     ksh-completion-type-var)))
+		;;
+		;; don't know. use 'em all
+		;;
+		(function (lambda (sym) t))))))
+	 ;;
+	 (completion (try-completion pattern ksh-completion-list predicate)))
+    ;;
+    (cond ((eq completion t))
+	  ;;
+	  ;; oops, what is this ?
+	  ;;
+          ((null completion)
+           (message "Can't find completion for \"%s\"" pattern))
+	  ;;
+	  ;; insert
+	  ;;
+          ((not (string= pattern completion))
+           (delete-region beg end)
+           (insert completion))
+	  ;;
+	  ;; write possible completion in the minibuffer,
+	  ;; use this instead of a seperate buffer (usual)
+	  ;;
+          (t
+           (let ((list (all-completions pattern ksh-completion-list predicate))
+		 (string ""))
+	     (while list
+	       (progn
+		 (setq string (concat string (format "%s " (car list))))
+		 (setq list (cdr list))))
+	     (message string))))))
+
+(provide 'ksh-mode)
+;;; ksh-mode.el ends here