(browse-url-browser-function)
[bpt/emacs.git] / lisp / progmodes / sh-script.el
index 336fd87..23d8374 100644 (file)
@@ -1,6 +1,6 @@
 ;;; sh-script.el --- shell-script editing commands for Emacs
 
-;; Copyright (C) 1993, 94, 95, 96, 97, 1999, 2001, 03, 2004
+;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2003, 2004, 2005
 ;;  Free Software Foundation, Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
@@ -414,6 +414,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.
+       ?$ "'"
        ?! "_"
        ?% "_"
        ?: "_"
@@ -448,6 +452,7 @@ This is buffer-local in every such buffer.")
     (define-key map "\C-c=" 'sh-set-indent)
     (define-key map "\C-c<" 'sh-learn-line-indent)
     (define-key map "\C-c>" 'sh-learn-buffer-indent)
+    (define-key map "\C-c\C-\\" 'sh-backslash-region)
 
     (define-key map "=" 'sh-assignment)
     (define-key map "\C-c+" 'sh-add)
@@ -495,10 +500,9 @@ This is buffer-local in every such buffer.")
 
 (defcustom sh-require-final-newline
   '((csh . t)
-    (pdksh . t)
-    (rc . require-final-newline)
-    (sh . require-final-newline))
+    (pdksh . t))
   "*Value of `require-final-newline' in Shell-Script mode buffers.
+\(SHELL . t) means use the value of `mode-require-final-newline' for SHELL.
 See `sh-feature'."
   :type '(repeat (cons (symbol :tag "Shell")
                       (choice (const :tag "require" t)
@@ -555,14 +559,24 @@ The actual command ends at the end of the first \\(grouping\\)."
 
 
 
-(defvar sh-here-document-word "EOF"
+(defcustom sh-here-document-word "EOF"
   "Word to delimit here documents.
-If the first character of this string is \"-\", this character will
-be removed from the string when it is used to close the here document.
-This convention is used by the Bash shell, for example, to indicate
-that leading tabs inside the here document should be ignored.
-Note that Emacs currently has no support for indenting inside here
-documents - you must insert literal tabs by hand.")
+If the first character of this string is \"-\", this is taken as
+part of the redirection operator, rather than part of the
+word (that is, \"<<-\" instead of \"<<\").  This is a feature
+used by some shells (for example Bash) to indicate that leading
+tabs inside the here document should be ignored.  In this case,
+Emacs indents the initial body and end of the here document with
+tabs, to the same level as the start (note that apart from this
+there is no support for indentation of here documents).  This
+will only work correctly if `sh-basic-offset' is a multiple of
+`tab-width'.
+
+Any quote characters or leading whitespace in the word are
+removed when closing the here document."
+  :type 'string
+  :group 'sh-script)
+
 
 (defvar sh-test
   '((sh "[  ]" . 3)
@@ -574,7 +588,7 @@ documents - you must insert literal tabs by hand.")
 ;; but it *did* have an asterisk in the docstring!
 (defcustom sh-builtins
   '((bash sh-append posix
-         "." "alias" "bg" "bind" "builtin" "compgen" "complete"
+         "." "alias" "bg" "bind" "builtin" "caller" "compgen" "complete"
           "declare" "dirs" "disown" "enable" "fc" "fg" "help" "history"
           "jobs" "kill" "let" "local" "popd" "printf" "pushd" "shopt"
           "source" "suspend" "typeset" "unalias")
@@ -778,8 +792,11 @@ See `sh-feature'.")
 \f
 ;; Font-Lock support
 
-(defface sh-heredoc-face
-  '((((class color)
+(defface sh-heredoc
+  '((((min-colors 88) (class color)
+      (background dark))
+     (:foreground "yellow1" :weight bold))
+    (((class color)
       (background dark))
      (:foreground "yellow" :weight bold))
     (((class color)
@@ -789,8 +806,14 @@ 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
   '((csh sh-append shell
@@ -810,12 +833,14 @@ See `sh-feature'.")
        ;; Function names.
        ("^\\(\\sw+\\)[ \t]*(" 1 font-lock-function-name-face)
        ("\\<\\(function\\)\\>[ \t]*\\(\\sw+\\)?"
-         (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t)))
+         (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t))
+       ("\\(?:^\\s *\\|[[();&|]\\s *\\|\\(?:\\s +-[ao]\\|if\\|else\\|then\\|while\\|do\\)\\s +\\)\\(!\\)"
+        1 font-lock-negation-char-face))
 
     ;; The next entry is only used for defining the others
     (shell sh-append executable-font-lock-keywords
            ;; Using font-lock-string-face here confuses sh-get-indent-info.
-           ("\\\\$" 0 font-lock-warning-face)
+           ("\\(^\\|[^\\]\\)\\(\\\\\\\\\\)*\\(\\\\\\)$" 3 'sh-escaped-newline)
           ("\\\\[^A-Za-z0-9]" 0 font-lock-string-face)
           ("\\${?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\|[$*_]\\)" 1
             font-lock-variable-name-face))
@@ -838,7 +863,7 @@ See `sh-feature'.")
 (defconst sh-st-symbol (string-to-syntax "_"))
 (defconst sh-here-doc-syntax (string-to-syntax "|")) ;; generic string
 
-(defconst sh-here-doc-open-re "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\|\\s_\\)+\\).*\\(\n\\)")
+(defconst sh-here-doc-open-re "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\)+\\).*\\(\n\\)")
 
 (defvar sh-here-doc-markers nil)
 (make-variable-buffer-local 'sh-here-doc-markers)
@@ -992,7 +1017,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
@@ -1021,7 +1046,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)
 
@@ -1065,7 +1090,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")
@@ -1081,75 +1106,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 '*
-  "*How much to indent a do statement.
-This is relative to the statement before the do, i.e. the
-while until or for statement."
+(defcustom sh-indent-for-do 0
+  "*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)
 
@@ -1161,7 +1186,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)
 
@@ -1173,17 +1198,27 @@ 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)
 
+(defcustom sh-backslash-column 48
+  "*Column in which `sh-backslash-region' inserts backslashes."
+  :type 'integer
+  :group 'sh)
+
+(defcustom sh-backslash-align t
+  "*If non-nil, `sh-backslash-region' will align backslashes."
+  :type 'boolean
+  :group 'sh)
+
 ;; Internal use - not designed to be changed by the user:
 
 (defun sh-mkword-regexpr (word)
@@ -1356,10 +1391,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)
@@ -1485,8 +1520,8 @@ Calls the value of `sh-set-shell-hook' if set."
            (executable-set-magic shell (sh-feature sh-shell-arg)
                                  no-query-flag insert-flag)))
   (let ((tem (sh-feature sh-require-final-newline)))
-    (unless (eq tem 'require-final-newline)
-      (setq require-final-newline tem)))
+    (if (eq tem t)
+       (setq require-final-newline mode-require-final-newline)))
   (setq
        comment-start-skip "#+[\t ]*"
        local-abbrev-table sh-mode-abbrev-table
@@ -2257,7 +2292,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))
     ))
@@ -2378,7 +2413,7 @@ Optional parameter DEPTH (usually 1) says how many to look for."
 (defun sh-var-value (var &optional ignore-error)
   "Return the value of variable VAR, interpreting symbols.
 It can also return t or nil.
-If an illegal value is found, throw an error unless Optional argument
+If an invalid value is found, throw an error unless Optional argument
 IGNORE-ERROR is non-nil."
   (let ((val (symbol-value var)))
     (cond
@@ -3230,7 +3265,7 @@ t means to return a list of all possible completions of STRING.
    (let ((sh-add-buffer (current-buffer)))
      (list (completing-read "Variable: " 'sh-add-completer)
           (prefix-numeric-value current-prefix-arg))))
-  (insert (sh-feature '((bash . "$[ ")
+  (insert (sh-feature '((bash . "$(( ")
                        (ksh88 . "$(( ")
                        (posix . "$(( ")
                        (rc . "`{expr $")
@@ -3239,7 +3274,7 @@ t means to return a list of all possible completions of STRING.
          (sh-remember-variable var)
          (if (< delta 0) " - " " + ")
          (number-to-string (abs delta))
-         (sh-feature '((bash . " ]")
+         (sh-feature '((bash . " ))")
                        (ksh88 . " ))")
                        (posix . " ))")
                        (rc . "}")
@@ -3465,7 +3500,8 @@ option followed by a colon `:' if the option accepts an argument."
       "esac" >
       \n "done"
       > \n
-      "shift " (sh-add "OPTIND" -1) \n))
+      "shift " (sh-add "OPTIND" -1) \n
+      "OPTIND=1" \n))
 
 
 
@@ -3483,7 +3519,6 @@ option followed by a colon `:' if the option accepts an argument."
             (match-string 1))))))
 
 
-
 (defun sh-maybe-here-document (arg)
   "Insert self.  Without prefix, following unquoted `<' inserts here document.
 The document is bounded by `sh-here-document-word'."
@@ -3494,18 +3529,21 @@ The document is bounded by `sh-here-document-word'."
       (save-excursion
        (backward-char 2)
        (sh-quoted-p))
-      (progn
+      (let ((tabs (if (string-match "\\`-" sh-here-document-word)
+                      (make-string (/ (current-indentation) tab-width) ?\t)
+                    ""))
+            (delim (replace-regexp-in-string "['\"]" ""
+                                            sh-here-document-word)))
        (insert sh-here-document-word)
        (or (eolp) (looking-at "[ \t]") (insert ? ))
        (end-of-line 1)
        (while
            (sh-quoted-p)
          (end-of-line 2))
-       (newline)
+       (insert ?\n tabs)
        (save-excursion
-          (insert ?\n (substring
-                       sh-here-document-word
-                       (if (string-match "^-" sh-here-document-word) 1 0)))))))
+          (insert ?\n tabs (replace-regexp-in-string
+                            "\\`-?[ \t]*" "" delim))))))
 
 \f
 ;; various other commands
@@ -3548,7 +3586,78 @@ The document is bounded by `sh-here-document-word'."
   (if (re-search-forward sh-end-of-command nil t)
       (goto-char (match-end 1))))
 
+;; Backslashification.  Stolen from make-mode.el.
+
+(defun sh-backslash-region (from to delete-flag)
+  "Insert, align, or delete end-of-line backslashes on the lines in the region.
+With no argument, inserts backslashes and aligns existing backslashes.
+With an argument, deletes the backslashes.
+
+This function does not modify the last line of the region if the region ends
+right at the start of the following line; it does not modify blank lines
+at the start of the region.  So you can put the region around an entire
+shell command and conveniently use this command."
+  (interactive "r\nP")
+  (save-excursion
+    (goto-char from)
+    (let ((column sh-backslash-column)
+          (endmark (make-marker)))
+      (move-marker endmark to)
+      ;; Compute the smallest column number past the ends of all the lines.
+      (if sh-backslash-align
+         (progn
+           (if (not delete-flag)
+               (while (< (point) to)
+                 (end-of-line)
+                 (if (= (preceding-char) ?\\)
+                     (progn (forward-char -1)
+                            (skip-chars-backward " \t")))
+                 (setq column (max column (1+ (current-column))))
+                 (forward-line 1)))
+           ;; Adjust upward to a tab column, if that doesn't push
+           ;; past the margin.
+           (if (> (% column tab-width) 0)
+               (let ((adjusted (* (/ (+ column tab-width -1) tab-width)
+                                  tab-width)))
+                 (if (< adjusted (window-width))
+                     (setq column adjusted))))))
+      ;; Don't modify blank lines at start of region.
+      (goto-char from)
+      (while (and (< (point) endmark) (eolp))
+        (forward-line 1))
+      ;; Add or remove backslashes on all the lines.
+      (while (and (< (point) endmark)
+                  ;; Don't backslashify the last line
+                  ;; if the region ends right at the start of the next line.
+                  (save-excursion
+                    (forward-line 1)
+                    (< (point) endmark)))
+        (if (not delete-flag)
+            (sh-append-backslash column)
+          (sh-delete-backslash))
+        (forward-line 1))
+      (move-marker endmark nil))))
+
+(defun sh-append-backslash (column)
+  (end-of-line)
+  ;; Note that "\\\\" is needed to get one backslash.
+  (if (= (preceding-char) ?\\)
+      (progn (forward-char -1)
+             (delete-horizontal-space)
+             (indent-to column (if sh-backslash-align nil 1)))
+    (indent-to column (if sh-backslash-align nil 1))
+    (insert "\\")))
+
+(defun sh-delete-backslash ()
+  (end-of-line)
+  (or (bolp)
+      (progn
+       (forward-char -1)
+       (if (looking-at "\\\\")
+           (delete-region (1+ (point))
+                          (progn (skip-chars-backward " \t") (point)))))))
+
 (provide 'sh-script)
 
-;;; arch-tag: eccd8b72-f337-4fc2-ae86-18155a69d937
+;; arch-tag: eccd8b72-f337-4fc2-ae86-18155a69d937
 ;;; sh-script.el ends here