home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / modes / ksh-mode.el < prev    next >
Encoding:
Text File  |  1993-04-07  |  24.0 KB  |  797 lines

  1. ;;;;;;;;;;;;;;;;;;;;;;;;;;; -*- Mode: Emacs-Lisp -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;
  2. ;; @(#)ksh-mode.el 1.22: sh (ksh, bash) script editing mode for GNU Emacs.
  3. ;; Copyright (C) 1992 Gary Ellison.
  4. ;; ksh-mode.el --  
  5. ;; Author: Gary F. Ellison <Gary_F_Ellison@ATT.COM>
  6. ;;                   AT&T Bell Laboratories
  7. ;;                   6200 East Broad Street
  8. ;;                   Columbus, Ohio 43213 USA
  9. ;;
  10. ;; Maintainer: Gary F. Ellison <Gary_F_Ellison@ATT.COM>
  11. ;; Created: Fri Jun 19
  12. ;; Version: 1.22
  13. ;; Keywords: shell, korn, bourne, sh, ksh, bash
  14. ;;
  15. ;; Delta On        : 3/29/93
  16. ;; Last Modified By: Dave Brennan
  17. ;; Last Modified On: Thu Apr  8 12:49:32 1993
  18. ;; Update Count    : 12
  19. ;; Status          : Highly Functional
  20. ;;
  21. ;; HISTORY 
  22. ;; 29-Mar-1993        Gary Ellison    
  23. ;;    Last Modified: Tue Sep 29 16:14:02 1992 #10 (Gary Ellison)
  24. ;;    Integrate line continuation patch supplied by
  25. ;;    Haavard Rue <hrue@imf.unit.no>
  26. ;;    Name back to ksh-mode to avoid confusion with sh-mode
  27. ;;    by Thomas W. Strong, Jr. <strong+@cmu.edu>.
  28. ;;
  29. ;; 29-Sep-1992        Gary Ellison    
  30. ;;    Last Modified: Wed Sep  2 08:51:40 1992 #9 (Gary Ellison)
  31. ;;    Full support of ksh88 case items. 
  32. ;;    Align statements under "do" and "then" keywords one position 
  33. ;;    past the keyword.
  34. ;;
  35. ;; 2-Sep-1992        Gary Ellison    
  36. ;;    Last Modified: Tue Aug  4 14:34:35 1992 #8 (Gary Ellison)
  37. ;;    Use make-variable-buffer-local instead of make-local-variable
  38. ;;    Get rid of superflous ksh-default variables.
  39. ;;    Use end of word match \b for "then", "do", "else", "elif"
  40. ;;    Support process substitution lists and exclude ksh 88 case items
  41. ;;    Use default-tab-width for indentation defaults.
  42. ;;    Moved installation instructions to the mode level documentation 
  43. ;;    section.
  44. ;;    Fixed auto-mode-alist documentation.
  45. ;;
  46. ;; 24-Jul-1992        Gary Ellison    
  47. ;;    Last Modified: Fri Jul 24 09:45:11 1992 #7 (Gary Ellison)
  48. ;;    Modified ksh-indent-region to use marker versus fixed end point.
  49. ;;    comment-start-skip regexp no longer fooled by parameter substitution.
  50. ;;    Added constant ksh-mode-version.
  51. ;;
  52. ;; 21-Jul-1992        Gary Ellison    
  53. ;;    Last Modified: Tue Jul 21 15:53:57 1992 #6 (Gary Ellison)
  54. ;;    Indent with tabs instead of spaces.
  55. ;;    Can handle just about all styles.
  56. ;;    Anti-newline in REs.
  57. ;;    Word delim "\b" in REs
  58. ;;    More syntax entries.
  59. ;;    Variables with regexp suffix abbreviated to re
  60. ;;    Better } handling
  61. ;;    Implemented minimal indent-region-function
  62. ;;    Mode documentation corrected.
  63. ;;    Minor lisp source format changes.
  64. ;;    
  65. ;; 29-Jun-1992        Gary Ellison    
  66. ;;    Last Modified: Mon Jun 29 15:39:35 1992 #5 (Gary Ellison)
  67. ;;    Optimize line-to-string
  68. ;;    Implicit/Explicit functions aok
  69. ;;    More indentation variables
  70. ;;    Superfluous defun killed.
  71. ;;    renamed to sh-mode
  72. ;;    
  73. ;; 22-Jun-1992          Gary Ellison
  74. ;;    Last Modified: Mon Jun 22 15:01:14 1992 #4 (Gary Ellison)
  75. ;;    Cleanup pre att.emacs posting
  76. ;;
  77. ;; 19-Jun-1992          Gary Ellison
  78. ;;    Last Modified: Fri Jun 19 17:19:14 1992 #3 (Gary Ellison)
  79. ;;    Minimal case indent handling
  80. ;;
  81. ;; 19-Jun-1992          Gary Ellison
  82. ;;    Last Modified: Fri Jun 19 16:23:26 1992 #2 (Gary Ellison)
  83. ;;    Nesting handled except for case statement
  84. ;;
  85. ;; 19-Jun-1992          Gary Ellison
  86. ;;    Last Modified: Fri Jun 19 10:03:07 1992 #1 (Gary Ellison)
  87. ;;    Conception of this mode.
  88. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  89. ;;
  90. ;; This file is compatible with GNU Emacs but is not part of the official
  91. ;; distribution.
  92. ;;
  93. ;; This program is free software; you can redistribute it and/or modify
  94. ;; it at your option.
  95. ;;   
  96. ;; Gary Ellison makes no representations about the suitability
  97. ;; of this software for any purpose.  It is provided "as is" without
  98. ;; express or implied warranty.
  99. ;;
  100. ;; This program is distributed in the hope that it will be useful,
  101. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  102. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  103. ;;   
  104. ;; LCD Archive Entry:
  105. ;; ksh-mode|Gary F. Ellison|Gary_F_Ellison@ATT.COM|
  106. ;; Mode for editing sh/ksh/bash scripts|
  107. ;; 29-Mar-1993|1.22|~/modes/ksh-mode.el.Z|
  108. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  109. ;;
  110. ;; Description:
  111. ;;   sh, ksh, and bash script editing commands for emacs.
  112. ;; 
  113. ;; Installation:
  114. ;;   Put ksh-mode.el in some directory in your load-path.
  115. ;;   Refer to the installation section of ksh-mode's function definition.
  116. ;;
  117. ;; Usage:
  118. ;;   This major mode assists shell script writers with indentation
  119. ;;   control and control structure construct matching in much the same
  120. ;;   fashion as other programming language modes. Invoke describe-mode
  121. ;;   for more information.
  122. ;; 
  123. ;; Bugs:
  124. ;;   Function ending brace "}" must be on a separate line for indent-line
  125. ;;   to do the right thing.
  126. ;;
  127. ;;   Explicit function definition matching will proclaim in the minibuffer
  128. ;;   "No matching compound command" followed by "Matched ... "
  129. ;;
  130. ;;   indent-for-comment fails to recognize a comment starting in column 0,
  131. ;;   hence it moves the comment-start in comment-column.
  132. ;;
  133. ;;========================================================================
  134.  
  135. (defconst ksh-mode-version "1.22"
  136.   "*Version numbers of this version of ksh-mode")
  137. ;;
  138. ;; Context/indentation regular expressions
  139. ;; 
  140. ;; indenting expressions
  141. ;;
  142. (defconst ksh-then-do-re     "^[^#'`\"\n]*\\b\\(then\\|do\\)\\b"
  143.   "*Regexp used to locate grouping keywords: \"then\" and \"do\"" )
  144.  
  145. (defconst ksh-do-re          "^[ \t]*\\bdo\\(\\b\\|$\\)"
  146.   "*Regexp used to match keyword: do")
  147.  
  148. (defconst ksh-then-re        "^[ \t]*\\bthen\\(\\b\\|$\\)"
  149.   "*Regexp used to match keyword: then")
  150.  
  151. ;;
  152. ;; Structure starting/indenting keywords
  153. ;;
  154. (defconst ksh-else-re           "^[ \t]*\\belse\\(\\b\\|$\\)"
  155.   "*Regexp used to match keyword: else")
  156.  
  157. (defconst ksh-elif-re           "^[ \t]*\\belif\\(\\b\\|$\\)"
  158.   "*Regexp used to match keyword: elif")
  159.  
  160. (defconst ksh-brace-re           "^[^#\n]*{[ \t\n]"
  161.   "*Regexp used to match syntactic entity: { ")
  162.  
  163. (defconst ksh-case-item-end-re           "^[^#\n]*;;[ \t\n]"
  164.   "*Regexp used to match case item end syntactic entity: ;;")
  165.  
  166. (defconst ksh-keywords-re
  167.   "^[^#'`\"\n]*\\b\\(else\\|if\\|elif\\|case\\|while\\|for\\|until\\|select\\)\\b"
  168.   "*Regexp used to detect compound command keywords: if, else, elif case, 
  169. while, for, until, and select")
  170.  
  171. (defconst ksh-if-re         "^[^#'`\"\n]*\\b\\(if\\)\\b"
  172.   "*Regexp used to match keyword: if")
  173.  
  174. (defconst ksh-wufs-re 
  175.   "^[^#'`\"\n]*\\b\\(while\\|for\\|until\\|select\\)\\b"
  176.   "*Match one of the keywords: while, until, for, select")
  177.  
  178. (defconst ksh-case-re           "^[^#'`\"\n]*\\b\\(case\\)\\b"
  179.   "*Regexp used to match keyword: case")
  180.  
  181. (defconst ksh-explicit-func-re
  182.   "^[ \t]*\\(function[ \t][a-zA-z_][a-zA-Z0-1]*\\)\\b"
  183.   "*Match an explicit function definition: function name")
  184.  
  185. (defconst ksh-implicit-func-re
  186.   "^[ \t]*\\([a-zA-z_][a-zA-Z0-1_]*\\)[ \t]*()[ \t]*"
  187.   "*Match an implicit function definition: name ()")
  188.  
  189. (defconst ksh-func-brace-re "^[ \t]*\\(.*{\\)[ \t\n]+"
  190.   "*Match a implicit function definition brace: name { ")
  191.  
  192. ;;
  193. ;; indenting 
  194. (defconst ksh-case-item-re           "^[^#`'\"\n]*\\()\\)"
  195.   "*Regexp used to match case-items including ksh88")
  196.  
  197. (defconst ksh-paren-re           "^[^#`'\"\n]*)[ \t\n]+"
  198.   "*Regexp used to match compound list & case items")
  199.  
  200. ;;
  201. ;; structure ending keyword regular expressions
  202. (defconst ksh-fi-re            "^[ \t]*fi\\b"
  203.   "*Regexp used to match keyword: fi")
  204.  
  205. (defconst ksh-esac-re          "^[ \t]*esac\\b"
  206.   "*Regexp used to match keyword: esac")
  207.  
  208. (defconst ksh-done-re          "^[ \t]*done\\b"
  209.   "*Regexp used to match keyword: done")
  210.  
  211. (defconst ksh-brace-end-re  "^[ \t]*}[ \t]*"
  212.   "*Regexp used to match function brace-groups")
  213.  
  214. ;;
  215. ;; Variables controlling indentation style
  216. ;;
  217. (defvar ksh-indent default-tab-width)
  218. (defvar ksh-case-item-indent default-tab-width)
  219. (defvar ksh-case-indent nil)
  220. (defvar ksh-group-indent (- 0 default-tab-width))
  221. (defvar ksh-brace-indent 0)
  222. (defvar ksh-match-and-tell t)
  223.  
  224. ;;
  225. ;; Create mode specific tables
  226. (defvar ksh-mode-syntax-table nil
  227.   "Syntax table used while in sh mode.")
  228. (if ksh-mode-syntax-table
  229.     ()
  230.   (setq ksh-mode-syntax-table (make-syntax-table))
  231.   (modify-syntax-entry ?\' "\"" ksh-mode-syntax-table)
  232.   (modify-syntax-entry ?` "\"" ksh-mode-syntax-table)
  233.   (modify-syntax-entry ?\n ">   " ksh-mode-syntax-table)
  234.   (modify-syntax-entry ?\f ">   " ksh-mode-syntax-table)
  235.   (modify-syntax-entry ?# "<   " ksh-mode-syntax-table)
  236.   (modify-syntax-entry ?_ "w" ksh-mode-syntax-table)
  237.   (modify-syntax-entry ?< "." ksh-mode-syntax-table)
  238.   (modify-syntax-entry ?> "." ksh-mode-syntax-table)
  239.   (modify-syntax-entry ?& "." ksh-mode-syntax-table)
  240.   (modify-syntax-entry ?| "." ksh-mode-syntax-table)
  241.   (modify-syntax-entry ?$ "." ksh-mode-syntax-table)
  242.   (modify-syntax-entry ?% "." ksh-mode-syntax-table)
  243.   (modify-syntax-entry ?= "." ksh-mode-syntax-table)
  244.   (modify-syntax-entry ?/ "." ksh-mode-syntax-table)
  245.   (modify-syntax-entry ?+ "." ksh-mode-syntax-table)
  246.   (modify-syntax-entry ?* "." ksh-mode-syntax-table)
  247.   (modify-syntax-entry ?- "." ksh-mode-syntax-table)
  248.   (modify-syntax-entry ?; "." ksh-mode-syntax-table)
  249.   )
  250.  
  251. (defvar ksh-mode-abbrev-table nil
  252.   "Abbrev table used while in sh mode.")
  253. (define-abbrev-table 'ksh-mode-abbrev-table ())
  254.  
  255. (defvar ksh-mode-map nil 
  256.   "Keymap used in sh mode")
  257.  
  258. (if ksh-mode-map
  259.     ()
  260.   (setq ksh-mode-map (make-sparse-keymap))
  261.   (define-key ksh-mode-map "\C-i"    'ksh-indent-line)
  262.   (define-key ksh-mode-map "\177"    'backward-delete-char-untabify)
  263.   (define-key ksh-mode-map "\C-j"    'reindent-then-newline-and-indent)
  264.   )
  265.  
  266.  
  267. (defun ksh-mode ()
  268.   "Major mode for editing (Bourne, Korn or Bourne again) shell scripts.
  269. Special key bindings and commands:
  270. \\{ksh-mode-map}
  271. Variables controlling indentation style:
  272. ksh-indent
  273.     Additional indentation of the first statement under keyword.
  274.     Default value is 8.
  275. ksh-case-indent
  276.     Additional indentation for statements under case items.
  277.     Default value is nil which will align the statements one position 
  278.     past the \")\" of the pattern.
  279. ksh-case-item-indent
  280.     Additional indentation for case items within a case statement.
  281.     Default value is 8.
  282. ksh-group-indent
  283.     Additional indentation for keywords \"do\" and \"then\".
  284.     Default value is -8.
  285. ksh-brace-indent
  286.     Indentation of { under a function definition.
  287.     Default value is 0.
  288. ksh-match-and-tell
  289.     If non-nil echo in the minibuffer the matching compound command
  290.     for the \"done\", \"}\", \"fi\", or \"esac\". Default value is t.
  291.  
  292. Style Guide.
  293.  By setting
  294.     (setq ksh-indent default-tab-width)
  295.     (setq ksh-group-indent 0)
  296.  
  297.     The following style is obtained:
  298.  
  299.     if [ -z $foo ]
  300.         then bar        <-- ksh-group-indent is additive to ksh-indent
  301.              foo
  302.     fi
  303.  
  304.  By setting
  305.     (setq ksh-indent default-tab-width)
  306.     (setq ksh-group-indent (- 0 ksh-indent))
  307.  
  308.     The following style is obtained:
  309.  
  310.     if [ -z $foo ]
  311.     then bar
  312.          foo
  313.     fi
  314.  
  315.  By setting
  316.     (setq ksh-case-item-indent 1)
  317.     (setq ksh-case-indent nil)
  318.  
  319.     The following style is obtained:
  320.  
  321.     case x in *
  322.      foo) bar           <-- ksh-case-item-indent
  323.           baz;;         <-- ksh-case-indent aligns with \")\"
  324.      foobar) foo
  325.              bar;;
  326.     esac
  327.  
  328.  By setting
  329.     (setq ksh-case-item-indent 1)
  330.     (setq ksh-case-indent 6)
  331.  
  332.     The following style is obtained:
  333.  
  334.     case x in *
  335.      foo) bar           <-- ksh-case-item-indent
  336.            baz;;        <-- ksh-case-indent
  337.      foobar) foo
  338.            bar;;
  339.     esac
  340.     
  341.  
  342. Installation:
  343.   Put ksh-mode.el in some directory in your load-path.
  344.   Put the following forms in your .emacs file.
  345.  
  346.  (autoload 'ksh-mode \"ksh-mode\" \"Major mode for editing sh Scripts.\" t)
  347.  
  348.  (setq auto-mode-alist
  349.       (append auto-mode-alist
  350.           (list
  351.            '(\"\\\\.sh$\" . ksh-mode)
  352.            '(\"\\\\.ksh$\" . ksh-mode)
  353.                '(\"\\\\.bashrc\" . ksh-mode)
  354.                '(\"\\\\..*profile\" . ksh-mode))))
  355.  
  356.  (setq ksh-mode-hook
  357.       (function (lambda ()
  358.          (setq ksh-indent 8)
  359.      (setq ksh-group-indent -8))
  360.      (setq ksh-brace-indent 0)   
  361.          (setq ksh-match-and-tell t)
  362.      )))
  363.  
  364. "
  365.   (interactive)
  366.   (kill-all-local-variables)
  367.   (use-local-map ksh-mode-map)
  368.   (setq major-mode 'ksh-mode)
  369.   (setq mode-name "Ksh")
  370.   (setq local-abbrev-table ksh-mode-abbrev-table)
  371.   (set-syntax-table ksh-mode-syntax-table)
  372.   (make-variable-buffer-local 'ksh-indent)
  373.   (make-variable-buffer-local 'ksh-case-item-indent)
  374.   (make-variable-buffer-local 'ksh-case-indent)
  375.   (make-variable-buffer-local 'ksh-group-indent)
  376.   (make-variable-buffer-local 'ksh-brace-indent)
  377.   (make-variable-buffer-local 'ksh-match-and-tell)
  378.   (make-variable-buffer-local 'indent-line-function)
  379.   (setq indent-line-function 'ksh-indent-line)
  380.   (make-variable-buffer-local 'indent-region-function)
  381.   (setq indent-region-function 'ksh-indent-region)
  382.   (make-variable-buffer-local 'comment-start)
  383.   (setq comment-start "# ")
  384.   (make-variable-buffer-local 'comment-end)
  385.   (setq comment-end "")
  386.   (make-variable-buffer-local 'comment-column)
  387.   (setq comment-column 32)
  388.   (make-variable-buffer-local 'comment-start-skip)
  389.   (setq comment-start-skip "#+ *")
  390.   (run-hooks 'ksh-mode-hook)
  391.   ) ;; defun
  392.  
  393. ;;
  394. ;; Support functions
  395.  
  396. (defun ksh-indentation-on-this-line ()
  397.   "Return current indentation level (no. of columns) that this line is
  398. indented"
  399.   (save-excursion
  400.     (back-to-indentation)
  401.     (current-column))
  402.   ) ;; defun
  403.  
  404. (defun ksh-current-line ()
  405.   "Return the vertical position of point in the buffer.
  406. Top line is 1."
  407.   (+ (count-lines (point-min) (point))
  408.      (if (= (current-column) 0) 1 0))
  409.   )
  410.  
  411.  
  412. (defun ksh-line-to-string ()
  413.   "From point, construct a string from all characters on
  414. current line"
  415.   (skip-chars-forward " \t") ;; skip tabs as well as spaces
  416.   (buffer-substring (point)
  417.                     (progn
  418.                       (end-of-line 1)
  419.                       (point))))
  420.  
  421. (defun ksh-get-nest-level ()
  422.   "Return a 2 element list (nest-level nest-line) describing where the
  423. current line should nest."
  424.   (let (
  425.         (level)
  426.     (anchor (point))
  427.         );; bind
  428.     (save-excursion
  429.       (forward-line -1)
  430.       (while (and (not (bobp))
  431.           (null level))
  432.     (if (and (not (looking-at "^[ \t]*$"))
  433. ;;; Rue
  434.           (not (save-excursion
  435.              (forward-line -1)
  436.              (beginning-of-line)
  437.              (looking-at "^.*\\\\$")))
  438. ;;; Rue
  439.          (not (looking-at "^[ \t]*#")))
  440.         (setq level (cons (ksh-indentation-on-this-line) 
  441.                   (ksh-current-line)))
  442.       (forward-line -1)
  443.       );; if
  444.     );; while
  445.       (if (null level)
  446.       (cons (ksh-indentation-on-this-line) (ksh-current-line))
  447.     level)
  448.       )
  449.     )
  450.   )
  451.  
  452. (defun ksh-looking-at-compound-list ()
  453.   "Return true if current line contains an indenting keyword"
  454.   (or 
  455.    (looking-at ksh-do-re)
  456.    (looking-at ksh-then-re)
  457.    ) ;; or
  458.   ) ;; defun
  459.  
  460. (defun ksh-looking-at-case-item ()
  461.   "Return true if current line is a case-item .vs. paren compound list"
  462.   (save-excursion
  463.     (beginning-of-line)
  464.     ;;
  465.     ;; Handle paren indentation constructs for this line
  466.     (cond ((looking-at ksh-paren-re)
  467.        (goto-line (cdr (ksh-get-nest-level)))
  468.        ;;
  469.        ;; The question is whether this is really a case item or just
  470.        ;; parenthesized compound list.
  471.        (cond ((or (looking-at ksh-case-re)
  472.               (looking-at ksh-case-item-end-re)))
  473.          ;;
  474.          ;; turns out to be a parenthesized compound list
  475.          ;; so propigate the nil for cond
  476.          )
  477.        ))
  478.     )
  479.   ) ;; defun
  480.  
  481. (defun ksh-get-case-indent ()
  482.   "Return the column of the closest open case statement"
  483.   (save-excursion
  484.     (let (
  485.       (nest-list (ksh-get-compound-level ksh-case-re ksh-esac-re (point)))
  486.       )
  487.       (if (null nest-list)
  488.       (progn 
  489.         (if ksh-match-and-tell
  490.         (message "No matching case for ;;"))
  491.         0)
  492.     (car nest-list)))
  493.     )
  494.   )
  495.  
  496. ;;
  497. ;; Functions which make this mode what it is
  498. ;;
  499.  
  500. (defun ksh-get-nester-column (nest-line)
  501.   "Return the column to indent to with respect to nest-line taking 
  502. into consideration keywords and other nesting constructs."
  503.   (save-excursion 
  504.     (let (
  505.       (this-line (ksh-current-line))
  506.       )
  507.       ;;
  508.       ;; Handle case item indentation constructs for this-line
  509.       (cond ((ksh-looking-at-case-item)
  510.          (goto-line nest-line)
  511.          (let (
  512.            (fence-post (save-excursion (end-of-line) (point)))
  513.            )
  514.            ;;
  515.            ;; Now know there is a case-item so detect whether
  516.            ;; it is first under case, just another case-item, or
  517.            ;; a case-item and case-item-end all rolled together.
  518.            ;;
  519.            (cond ((re-search-forward ksh-case-re fence-post t)
  520.               (goto-char (match-beginning 1))
  521.               (+ (current-column) ksh-case-item-indent))
  522.  
  523.              ((ksh-looking-at-case-item)
  524.               (ksh-indentation-on-this-line))
  525.  
  526.              ((looking-at ksh-case-item-end-re)
  527.               (end-of-line)
  528.               (+ (ksh-get-case-indent) ksh-case-item-indent))
  529.              )
  530.            ))
  531.         (t                
  532.          ;;
  533.          ;; this-line is not a case-item. So figure out what to do
  534.          ;; relative to the nest-line.
  535.          (goto-line nest-line)
  536.          (let* (
  537.             (fence-post (save-excursion (end-of-line) (point)))
  538.             (nester-column
  539.              (cond
  540.               ;; In order to locate the column of the keyword,
  541.               ;; which might be embedded within a case-item,
  542.               ;; it is necessary to use re-search-forward.
  543.               ((re-search-forward ksh-keywords-re fence-post t)
  544.                (goto-char (match-beginning 1))
  545.                (if (looking-at ksh-case-re)
  546.                (+ (current-column) ksh-case-item-indent)
  547.              (+ (current-column) ksh-indent)))
  548.  
  549.               ((re-search-forward ksh-then-do-re fence-post t)
  550.                (goto-char (match-end 1))
  551.                (+ (current-column) 1))
  552.  
  553.               ((looking-at ksh-brace-re)
  554.                (+ (ksh-indentation-on-this-line) ksh-indent))
  555.               ;;
  556.               ;; Forces functions to first column
  557.               ((or (looking-at ksh-implicit-func-re)
  558.                (looking-at ksh-explicit-func-re))
  559.                (if (looking-at ksh-func-brace-re)
  560.                ksh-indent
  561.              ksh-brace-indent))
  562.  
  563.               ;;
  564.               ;; Need to first detect the end of a case-item
  565.               ((looking-at ksh-case-item-end-re)
  566.                (end-of-line)
  567.                (+ (ksh-get-case-indent) ksh-case-item-indent))
  568.               ;;
  569.               ;; Now detect first statement under a case item
  570.               ((ksh-looking-at-case-item)
  571.                (if (null ksh-case-indent)
  572.                (progn
  573.                  (re-search-forward ksh-case-item-re fence-post t)
  574.                  (goto-char (match-end 1))
  575.                  (+ (current-column) 1))
  576.              (+ (ksh-indentation-on-this-line) ksh-case-indent)))
  577.  
  578.               ;;
  579.               (t (ksh-indentation-on-this-line)))
  580.              ))
  581.            (goto-line this-line)
  582.            ;;
  583.            ;; Handle additional indentation constructs for this line
  584.            (cond ((ksh-looking-at-compound-list)
  585.               (+ nester-column ksh-group-indent))
  586.              (t nester-column))
  587.            )))
  588.       )
  589.     );; excursion
  590.   );; defun
  591.  
  592.  
  593. (defun ksh-indent-line ()
  594.   "Indent current line as far as it should go according
  595. to the syntax/context"
  596.   (interactive)
  597.   (save-excursion
  598.     (beginning-of-line)
  599.     (if (bobp)
  600.         nil
  601.       ;;
  602.       ;; Align this line to current nesting level
  603.       (let*
  604.           (
  605.        (level-list (ksh-get-nest-level))     ; Where to nest against
  606.            (last-line-level (car level-list))
  607.            (this-line-level (ksh-indentation-on-this-line))
  608.            (level-diff (- this-line-level last-line-level))
  609.            )
  610.     (if (not (zerop level-diff))
  611.         (progn
  612.           (indent-to last-line-level)
  613.           (let ((beg (point)))
  614.         (back-to-indentation)
  615.         (delete-region beg (point)))
  616.           )
  617.       ) ;; if
  618.     ;;
  619.     ;; At this point this line is aligned with last line
  620.     ;; So given context of last line indent or leave alone
  621.         (let
  622.             (
  623.              (nester-column (ksh-get-nester-column (cdr level-list)))
  624.          )
  625.       (beginning-of-line)
  626.       (indent-to nester-column)
  627.       (let ((beg (point)))
  628.         (back-to-indentation)
  629.         (delete-region beg (point)))
  630.           ) ;; let
  631.     ;;
  632.     ;; Now unindent if need be
  633.     (ksh-match-structure-and-reindent) ;; match structures and reindent
  634.         ) ;; let*
  635.       ) ;; if
  636.     ) ;; excursion
  637.   ;;
  638.   ;; Position point on this line
  639.   (let*
  640.       (
  641.        (this-line-level (ksh-indentation-on-this-line))
  642.        (this-bol (save-excursion
  643.                    (beginning-of-line)
  644.                    (point)))
  645.        (this-point (- (point) this-bol))
  646.        )
  647.     (cond ((> this-line-level this-point) ;; point in initial white space
  648.            (back-to-indentation))
  649.            (t nil)
  650.            ) ;; cond
  651.     ) ;; let*
  652.   ) ;; defun
  653.  
  654.  
  655. (defun ksh-match-structure-and-reindent ()
  656.   "If the current line matches one of the indenting keywords
  657. or one of the control structure ending keywords then reindent. Also
  658. if ksh-match-and-tell is non-nil the matching structure will echo in
  659. the minibuffer"
  660.   (interactive)
  661.   (save-excursion
  662.     (beginning-of-line)
  663.     (cond ((looking-at ksh-else-re)
  664.        (ksh-match-indent-level ksh-if-re ksh-fi-re))
  665.       ((looking-at ksh-elif-re)
  666.        (ksh-match-indent-level ksh-if-re ksh-fi-re))
  667.       ((looking-at ksh-fi-re)
  668.        (ksh-match-indent-level ksh-if-re ksh-fi-re))
  669.       ((looking-at ksh-done-re)
  670.        (ksh-match-indent-level ksh-wufs-re ksh-done-re))
  671.       ((looking-at ksh-esac-re)
  672.        (ksh-match-indent-level ksh-case-re ksh-esac-re))
  673.       ;;
  674.       ((looking-at ksh-brace-end-re)
  675.        (cond
  676.         ((ksh-match-indent-level ksh-implicit-func-re ksh-brace-end-re))
  677.         ((ksh-match-indent-level ksh-explicit-func-re ksh-brace-end-re))
  678.         ((ksh-match-indent-level ksh-func-brace-re ksh-brace-end-re))
  679.         (t nil)))
  680.       (t nil)
  681.       );; cond
  682.     )
  683.   )
  684.  
  685. (defun ksh-match-indent-level (begin-re end-re)
  686.   "Match the compound command and indent. Return nil on no match, t otherwise"
  687.   (interactive)
  688.   (let* (
  689.      (nest-list 
  690.       (save-excursion
  691.         (ksh-get-compound-level begin-re end-re (point))
  692.         ))
  693.      ) ;; bindings
  694.     (if (null nest-list)
  695.     (progn 
  696.       (if ksh-match-and-tell
  697.           (message "No matching compound command"))
  698.       nil) ;; propagate a miss
  699.       (progn
  700.     (let* (
  701.            (nest-level (car nest-list))
  702.            (match-line (cdr nest-list))
  703.            (diff-level (- (ksh-indentation-on-this-line) nest-level))
  704.            ) ;; bindings
  705.         (if ksh-match-and-tell
  706.         (save-excursion
  707.           (goto-line match-line)
  708.           (message (format "Matched ... %s" (ksh-line-to-string)))
  709.           ) ;; excursion
  710.           )
  711.       (save-excursion
  712.         (if (not (zerop diff-level))
  713.         (progn
  714.           (beginning-of-line)
  715.           (indent-to nest-level)
  716.           (let ((beg (point)))
  717.             (back-to-indentation)
  718.             (delete-region beg (point)))
  719.           )
  720.           ) ;; if
  721.         ) ;; excursion
  722.       ) ;; let* nest-line
  723.     t) ;; progn propagate a hit
  724.       ) ;; if
  725.     ) ;; let* nest-list
  726.   ) ;; defun ksh-match-indent-level 
  727.  
  728. (defun ksh-get-compound-level 
  729.   (begin-re end-re anchor-point &optional balance-list)
  730.   "Determine how much to indent this structure. Return a list (level line) 
  731. of the matching compound command or nil if no match found."
  732.   (let* 
  733.       (;; Locate the next compound begin keyword bounded by point-min
  734.        (match-point (if (re-search-backward begin-re (point-min) t)
  735.             (match-beginning 1) 0))
  736.        (nest-column (if (zerop match-point)
  737.             1 
  738.               (progn
  739.             (goto-char match-point)
  740.             (current-column))))
  741.        (nest-list (cons 0 0))    ;; sentinel cons since cdr is >= 1
  742.        )
  743.     (if (zerop match-point)
  744.     nil ;; graceful exit from recursion
  745.       (progn
  746.     (if (nlistp balance-list)
  747.         (setq balance-list (list)))
  748.     ;; Now search forward from matching start keyword for end keyword
  749.     (while (and (consp nest-list) (zerop (cdr nest-list))
  750.             (re-search-forward end-re anchor-point t))
  751.       (if (not (memq (point) balance-list))
  752.           (progn
  753.         (setq balance-list (cons (point) balance-list))
  754.         (goto-char match-point)  ;; beginning of compound cmd
  755.         (setq nest-list
  756.               (ksh-get-compound-level begin-re end-re
  757.                          anchor-point balance-list))
  758.         )))
  759.  
  760.     (cond ((consp nest-list)
  761.            (if (zerop (cdr nest-list))
  762.          (progn
  763.            (goto-char match-point)
  764.            (cons nest-column (ksh-current-line)))
  765.          nest-list))
  766.           (t nil)
  767.           )
  768.     )
  769.       )
  770.     )
  771.   )
  772.  
  773.  
  774. (defun ksh-indent-region (start end)
  775.   "From start to end, indent each line."
  776.   ;;
  777.   ;; The algorithm is just moving through the region line by line with 
  778.   ;; the match noise turned off.
  779.   (save-excursion
  780.     (let ((match-and-tell ksh-match-and-tell)
  781.       (endmark (copy-marker end)))
  782.  
  783.       (setq ksh-match-and-tell nil)
  784.       (goto-char start)
  785.       (beginning-of-line)
  786.       (setq start (point))
  787.       (while (> (marker-position endmark) start)
  788.     (ksh-indent-line)
  789.     (forward-line 1)
  790.     (setq start (point)))
  791.  
  792.       (set-marker endmark nil)
  793.       (setq ksh-match-and-tell match-and-tell)
  794.       )
  795.     )
  796.   )
  797.