]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
(gnus-newsrc-file-version): Add defvar.
[gnu-emacs] / lisp / progmodes / sh-script.el
index 70172e22732beeabb821fe80dd09d586cda07097..4f702186685bf1eedfd2f52ad0c728b69dd92d76 100644 (file)
@@ -22,8 +22,8 @@
 
 ;; 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, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
   (require 'comint))
 (require 'executable)
 
+(defvar font-lock-comment-face)
+(defvar font-lock-set-defaults)
+(defvar font-lock-string-face)
 
 
 (defgroup sh nil
-  "Shell programming utilities"
+  "Shell programming utilities."
   :group 'unix
   :group 'languages)
 
 (defgroup sh-script nil
-  "Shell script mode"
+  "Shell script mode."
   :group 'sh
   :prefix "sh-")
 
@@ -414,6 +417,10 @@ This is buffer-local in every such buffer.")
        ?\" "\"\""
        ?\' "\"'"
        ?\` "\"`"
+       ;; ?$ might also have a ". p" syntax. Both "'" and ". p" seem
+       ;; to work fine. This is needed so that dabbrev-expand
+       ;; $VARNAME works.
+       ?$ "'"
        ?! "_"
        ?% "_"
        ?: "_"
@@ -788,7 +795,7 @@ See `sh-feature'.")
 \f
 ;; Font-Lock support
 
-(defface sh-heredoc-face
+(defface sh-heredoc
   '((((min-colors 88) (class color)
       (background dark))
      (:foreground "yellow1" :weight bold))
@@ -802,14 +809,16 @@ See `sh-feature'.")
      (:weight bold)))
   "Face to show a here-document"
   :group 'sh-indentation)
-(defvar sh-heredoc-face 'sh-heredoc-face)
+;; backward-compatibility alias
+(put 'sh-heredoc-face 'face-alias 'sh-heredoc)
+(defvar sh-heredoc-face 'sh-heredoc)
 
 (defface sh-escaped-newline '((t :inherit font-lock-string-face))
   "Face used for (non-escaped) backslash at end of a line in Shell-script mode."
   :group 'sh-script
   :version "22.1")
 
-(defvar sh-font-lock-keywords
+(defvar sh-font-lock-keywords-var
   '((csh sh-append shell
         ("\\${?[#?]?\\([A-Za-z_][A-Za-z0-9_]*\\|0\\)" 1
           font-lock-variable-name-face))
@@ -832,7 +841,7 @@ See `sh-feature'.")
         1 font-lock-negation-char-face))
 
     ;; The next entry is only used for defining the others
-    (shell sh-append executable-font-lock-keywords
+    (shell
            ;; Using font-lock-string-face here confuses sh-get-indent-info.
            ("\\(^\\|[^\\]\\)\\(\\\\\\\\\\)*\\(\\\\\\)$" 3 'sh-escaped-newline)
           ("\\\\[^A-Za-z0-9]" 0 font-lock-string-face)
@@ -844,11 +853,11 @@ See `sh-feature'.")
          ("^\\(\\sw+\\):"  1 font-lock-variable-name-face)))
   "Default expressions to highlight in Shell Script modes.  See `sh-feature'.")
 
-(defvar sh-font-lock-keywords-1
+(defvar sh-font-lock-keywords-var-1
   '((sh "[ \t]in\\>"))
   "Subdued level highlighting for Shell Script modes.")
 
-(defvar sh-font-lock-keywords-2 ()
+(defvar sh-font-lock-keywords-var-2 ()
   "Gaudy level highlighting for Shell Script modes.")
 
 ;; These are used for the syntax table stuff (derived from cperl-mode).
@@ -1011,7 +1020,7 @@ Anything else means:   whenever we have a \"good guess\" as to the value."
   :group 'sh-indentation)
 
 (defcustom sh-popup-occur-buffer nil
-  "*Controls when  `sh-learn-buffer-indent' pops the *indent* buffer.
+  "*Controls when  `sh-learn-buffer-indent' pops the `*indent*' buffer.
 If t it is always shown.  If nil, it is shown only when there
 are conflicts."
   :type '(choice
@@ -1040,7 +1049,7 @@ Can be set to a number, or to nil which means leave it as is."
 
 (defcustom sh-basic-offset 4
   "*The default indentation increment.
-This value is used for the + and - symbols in an indentation variable."
+This value is used for the `+' and `-' symbols in an indentation variable."
   :type 'integer
   :group 'sh-indentation)
 
@@ -1084,7 +1093,7 @@ a number means align to that column, e.g. 0 means fist column."
           :menu-tag "/   Indent left  half sh-basic-offset")))
 
 (defcustom sh-indent-for-else 0
-  "*How much to indent an else relative to an if.  Usually 0."
+  "*How much to indent an `else' relative to its `if'.  Usually 0."
   :type `(choice
          (integer :menu-tag "A number (positive=>indent right)"
                   :tag "A number")
@@ -1100,75 +1109,75 @@ a number means align to that column, e.g. 0 means fist column."
          sh-symbol-list))
 
 (defcustom sh-indent-for-fi 0
-  "*How much to indent a fi relative to an if.  Usually 0."
+  "*How much to indent a `fi' relative to its `if'.  Usually 0."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
-(defcustom sh-indent-for-done '0
-  "*How much to indent a done relative to its matching stmt.  Usually 0."
+(defcustom sh-indent-for-done 0
+  "*How much to indent a `done' relative to its matching stmt.  Usually 0."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
 (defcustom sh-indent-after-else '+
-  "*How much to indent a statement after an else statement."
+  "*How much to indent a statement after an `else' statement."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
 (defcustom sh-indent-after-if '+
-  "*How much to indent a statement after an if statement.
-This includes lines after else and elif statements, too, but
-does not affect then else elif or fi statements themselves."
+  "*How much to indent a statement after an `if' statement.
+This includes lines after `else' and `elif' statements, too, but
+does not affect the `else', `elif' or `fi' statements themselves."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
 (defcustom sh-indent-for-then 0
-  "*How much to indent a then relative to an if."
+  "*How much to indent a `then' relative to its `if'."
   :type `(choice ,@ sh-number-or-symbol-list )
   :group 'sh-indentation)
 
 (defcustom sh-indent-for-do 0
-  "*How much to indent a do statement.
-This is relative to the statement before the do, i.e. the
-while until or for statement."
+  "*How much to indent a `do' statement.
+This is relative to the statement before the `do', typically a
+`while', `until', `for', `repeat' or `select' statement."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
-(defcustom sh-indent-after-do '*
-  "*How much to indent a line after a do statement.
-This is used when the do is the first word of the line.
-This is relative to the statement before the do, e.g. a
-while for repeat or select statement."
+(defcustom sh-indent-after-do '+
+  "*How much to indent a line after a `do' statement.
+This is used when the `do' is the first word of the line.
+This is relative to the statement before the `do', typically a
+`while', `until', `for', `repeat' or `select' statement."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
 (defcustom sh-indent-after-loop-construct '+
   "*How much to indent a statement after a loop construct.
 
-This variable is used when the keyword \"do\" is on the same line as the
-loop statement (e.g.  \"until\", \"while\" or \"for\").
-If the do is on a line by itself, then `sh-indent-after-do' is used instead."
+This variable is used when the keyword `do' is on the same line as the
+loop statement (e.g., `until', `while' or `for').
+If the `do' is on a line by itself, then `sh-indent-after-do' is used instead."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
 
 (defcustom sh-indent-after-done 0
-  "*How much to indent a statement after a \"done\" keyword.
-Normally this is 0, which aligns the \"done\" to the matching
+  "*How much to indent a statement after a `done' keyword.
+Normally this is 0, which aligns the `done' to the matching
 looping construct line.
-Setting it non-zero allows you to have the \"do\" statement on a line
+Setting it non-zero allows you to have the `do' statement on a line
 by itself and align the done under to do."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
 (defcustom sh-indent-for-case-label '+
   "*How much to indent a case label statement.
-This is relative to the line containing the case statement."
+This is relative to the line containing the `case' statement."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
 (defcustom sh-indent-for-case-alt '++
   "*How much to indent statements after the case label.
-This is relative to the line containing the case statement."
+This is relative to the line containing the `case' statement."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
@@ -1180,7 +1189,7 @@ This is relative to the line containing the case statement."
 
 (defcustom sh-indent-after-open '+
   "*How much to indent after a line with an opening parenthesis or brace.
-For an open paren after a function `sh-indent-after-function' is used."
+For an open paren after a function, `sh-indent-after-function' is used."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
@@ -1192,13 +1201,13 @@ For an open paren after a function `sh-indent-after-function' is used."
 ;; These 2 are for the rc shell:
 
 (defcustom sh-indent-after-switch '+
-  "*How much to indent a case statement relative to the switch statement.
+  "*How much to indent a `case' statement relative to the `switch' statement.
 This is for the rc shell."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
 
 (defcustom sh-indent-after-case '+
-  "*How much to indent a statement relative to the case statement.
+  "*How much to indent a statement relative to the `case' statement.
 This is for the rc shell."
   :type `(choice ,@ sh-number-or-symbol-list)
   :group 'sh-indentation)
@@ -1358,9 +1367,12 @@ with your script for an edit-interpret-debug cycle."
        paragraph-start (concat page-delimiter "\\|$")
        paragraph-separate paragraph-start
        comment-start "# "
+       comment-start-skip "#+[\t ]*"
+       local-abbrev-table sh-mode-abbrev-table
        comint-dynamic-complete-functions sh-dynamic-complete-functions
        ;; we can't look if previous line ended with `\'
        comint-prompt-regexp "^[ \t]*"
+       imenu-case-fold-search nil
        font-lock-defaults
        `((sh-font-lock-keywords
           sh-font-lock-keywords-1 sh-font-lock-keywords-2)
@@ -1385,10 +1397,10 @@ with your script for an edit-interpret-debug cycle."
           (cond ((looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")
                  (match-string 2))
                 ((and buffer-file-name
-                      (string-match "\\.m?spec$" buffer-file-name))
+                      (string-match "\\.m?spec\\'" buffer-file-name))
                  "rpm")))))
     (sh-set-shell (or interpreter sh-shell-file) nil nil))
-  (run-hooks 'sh-mode-hook))
+  (run-mode-hooks 'sh-mode-hook))
 
 ;;;###autoload
 (defalias 'shell-script-mode 'sh-mode)
@@ -1397,13 +1409,14 @@ with your script for an edit-interpret-debug cycle."
 (defun sh-font-lock-keywords (&optional keywords)
   "Function to get simple fontification based on `sh-font-lock-keywords'.
 This adds rules for comments and assignments."
-  (sh-feature sh-font-lock-keywords
+  (sh-feature sh-font-lock-keywords-var
              (when (stringp (sh-feature sh-assignment-regexp))
                (lambda (list)
                  `((,(sh-feature sh-assignment-regexp)
                     1 font-lock-variable-name-face)
                    ,@keywords
-                   ,@list)))))
+                   ,@list
+                   ,@executable-font-lock-keywords)))))
 
 (defun sh-font-lock-keywords-1 (&optional builtins)
   "Function to get better fontification including keywords."
@@ -1420,10 +1433,10 @@ This adds rules for comments and assignments."
                         "\\>")
                (2 font-lock-keyword-face nil t)
                (6 font-lock-builtin-face))
-              ,@(sh-feature sh-font-lock-keywords-2)))
+              ,@(sh-feature sh-font-lock-keywords-var-2)))
         (,(concat keywords "\\)\\>")
          2 font-lock-keyword-face)
-        ,@(sh-feature sh-font-lock-keywords-1)))))
+        ,@(sh-feature sh-font-lock-keywords-var-1)))))
 
 (defun sh-font-lock-keywords-2 ()
   "Function to get better fontification including keywords and builtins."
@@ -1485,6 +1498,7 @@ This adds rules for comments and assignments."
      ("case" sh-handle-this-rc-case sh-handle-prev-rc-case))))
 
 
+
 (defun sh-set-shell (shell &optional no-query-flag insert-flag)
   "Set this buffer's shell to SHELL (a string).
 When used interactively, insert the proper starting #!-line,
@@ -1517,13 +1531,10 @@ Calls the value of `sh-set-shell-hook' if set."
     (if (eq tem t)
        (setq require-final-newline mode-require-final-newline)))
   (setq
-       comment-start-skip "#+[\t ]*"
-       local-abbrev-table sh-mode-abbrev-table
        mode-line-process (format "[%s]" sh-shell)
        sh-shell-variables nil
        sh-shell-variables-initialized nil
-       imenu-generic-expression (sh-feature sh-imenu-generic-expression)
-       imenu-case-fold-search nil)
+       imenu-generic-expression (sh-feature sh-imenu-generic-expression))
   (make-local-variable 'sh-mode-syntax-table)
   (let ((tem (sh-feature sh-mode-syntax-table-input)))
     (setq sh-mode-syntax-table
@@ -1551,10 +1562,13 @@ Calls the value of `sh-set-shell-hook' if set."
        (message "Indentation setup for shell type %s" sh-shell))
     (message "No indentation for this shell type.")
     (setq indent-line-function 'sh-basic-indent-line))
+  (when font-lock-mode
+    (setq font-lock-set-defaults nil)
+    (font-lock-set-defaults)
+    (font-lock-fontify-buffer))
   (run-hooks 'sh-set-shell-hook))
 
 
-
 (defun sh-feature (alist &optional function)
   "Index ALIST by the current shell.
 If ALIST isn't a list where every element is a cons, it is returned as is.
@@ -1572,39 +1586,38 @@ Else indexing follows an inheritance logic which works in two ways:
     one shell to be derived from another shell.
     The value thus determined is physically replaced into the alist.
 
-Optional FUNCTION is applied to the determined value and the result is cached
-in ALIST."
+If FUNCTION is non-nil, it is called with one argument,
+the value thus obtained, and the result is used instead."
   (or (if (consp alist)
+         ;; Check for something that isn't a valid alist.
          (let ((l alist))
            (while (and l (consp (car l)))
              (setq l (cdr l)))
            (if l alist)))
-      (if function
-         (cdr (assoc (setq function (cons sh-shell function)) alist)))
-      (let ((sh-shell sh-shell)
-           elt val)
-       (while (and sh-shell
-                   (not (setq elt (assq sh-shell alist))))
-         (setq sh-shell (cdr (assq sh-shell sh-ancestor-alist))))
-       ;; If the shell is not known, treat it as sh.
-       (unless elt
-         (setq elt (assq 'sh alist)))
-       (if (and (consp (setq val (cdr elt)))
-                (memq (car val) '(sh-append sh-modify)))
-           (setcdr elt
-                   (setq val
-                         (apply (car val)
-                                (let ((sh-shell (car (cdr val))))
-                                   (if (assq sh-shell alist)
-                                       (sh-feature alist)
-                                     (eval sh-shell)))
-                                (cddr val)))))
-       (if function
-           (nconc alist
-                  (list (cons function
-                              (setq sh-shell (car function)
-                                    val (funcall (cdr function) val))))))
-       val)))
+
+      (let ((orig-sh-shell sh-shell))
+       (let ((sh-shell sh-shell)
+             elt val)
+         (while (and sh-shell
+                     (not (setq elt (assq sh-shell alist))))
+           (setq sh-shell (cdr (assq sh-shell sh-ancestor-alist))))
+         ;; If the shell is not known, treat it as sh.
+         (unless elt
+           (setq elt (assq 'sh alist)))
+         (setq val (cdr elt))
+         (if (and (consp val)
+                  (memq (car val) '(sh-append sh-modify)))
+             (setq val
+                   (apply (car val)
+                          ;; Refer to the value for a different shell,
+                          ;; as a kind of inheritance.
+                          (let ((sh-shell (car (cdr val))))
+                            (sh-feature alist))
+                          (cddr val))))
+         (if function
+             (setq sh-shell orig-sh-shell
+                   val (funcall function val)))
+         val))))
 
 
 
@@ -2027,11 +2040,20 @@ STRING       This is ignored for the purposes of calculating
        ;; Continuation lines are handled specially
        (if (sh-this-is-a-continuation)
            (progn
-             ;; We assume the line being continued is already
-             ;; properly indented...
-             ;; (setq prev-line-end (sh-prev-line))
-             (setq align-point (sh-prev-line nil))
-             (setq result (list '(+ sh-indent-for-continuation)))
+              (setq result
+                    (if (save-excursion
+                          (beginning-of-line)
+                          (not (memq (char-before (- (point) 2)) '(?\s ?\t))))
+                        ;; By convention, if the continuation \ is not
+                        ;; preceded by a SPC or a TAB it means that the line
+                        ;; is cut at a place where spaces cannot be freely
+                        ;; added/removed.  I.e. do not indent the line.
+                        (list '(= nil))
+                      ;; We assume the line being continued is already
+                      ;; properly indented...
+                      ;; (setq prev-line-end (sh-prev-line))
+                      (setq align-point (sh-prev-line nil))
+                      (list '(+ sh-indent-for-continuation))))
              (setq have-result t))
          (beginning-of-line)
          (skip-chars-forward " \t")
@@ -2124,10 +2146,9 @@ STRING        This is ignored for the purposes of calculating
       (sh-debug "result is now: %s" result)
 
       (or result
-         (if prev-line-end
-             (setq result (list (list t prev-line-end)))
-           (setq result (list (list '= 'sh-first-lines-indent)))
-           ))
+         (setq result (list (if prev-line-end
+                                 (list t prev-line-end)
+                               (list '= 'sh-first-lines-indent)))))
 
       (if (eq result t)
          (setq result nil))
@@ -2286,7 +2307,7 @@ we go to the end of the previous line and do not check for continuations."
        (if (looking-at "[\"'`]")
            (sh-safe-forward-sexp)
          ;; (> (skip-chars-forward "^ \t\n\"'`") 0)
-         (> (skip-chars-forward "-_a-zA-Z\$0-9") 0)
+         (> (skip-chars-forward "-_a-zA-Z$0-9") 0)
          ))
     (buffer-substring start (point))
     ))
@@ -2364,7 +2385,7 @@ If AND-MOVE is non-nil then move to end of word."
        (goto-char where))
     (prog1
        (buffer-substring (point)
-                         (progn (skip-chars-forward "^ \t\n;")(point)))
+                         (progn (skip-chars-forward "^ \t\n;&")(point)))
       (unless and-move
        (goto-char start)))))
 
@@ -2689,11 +2710,9 @@ unless optional argument ARG (the prefix when interactive) is non-nil."
 
 (defun sh-mark-init (buffer)
   "Initialize a BUFFER to be used by `sh-mark-line'."
-  (save-excursion
-    (set-buffer (get-buffer-create buffer))
+  (with-current-buffer (get-buffer-create buffer)
     (erase-buffer)
-    (occur-mode)
-    ))
+    (occur-mode)))
 
 
 (defun sh-mark-line (message point buffer &optional add-linenum occur-point)
@@ -2966,8 +2985,7 @@ This command can often take a long time to run."
          (let ((var (car learned-var)))
            (sh-mark-line (format "  %s %s" var (symbol-value var))
                          (nth 2 learned-var) out-buffer)))
-       (save-excursion
-         (set-buffer out-buffer)
+       (with-current-buffer out-buffer
          (goto-char (point-min))
          (insert
           (format "Indentation values for buffer %s.\n" name)
@@ -3238,8 +3256,7 @@ nil means to return the best completion of STRING, or nil if there is none.
 t means to return a list of all possible completions of STRING.
 `lambda' means to return t if STRING is a valid completion as it stands."
   (let ((sh-shell-variables
-        (save-excursion
-          (set-buffer sh-add-buffer)
+        (with-current-buffer sh-add-buffer
           (or sh-shell-variables-initialized
               (sh-shell-initialize-variables))
           (nconc (mapcar (lambda (var)
@@ -3529,7 +3546,7 @@ The document is bounded by `sh-here-document-word'."
             (delim (replace-regexp-in-string "['\"]" ""
                                             sh-here-document-word)))
        (insert sh-here-document-word)
-       (or (eolp) (looking-at "[ \t]") (insert ? ))
+       (or (eolp) (looking-at "[ \t]") (insert ?\s))
        (end-of-line 1)
        (while
            (sh-quoted-p)