]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
(sh-font-lock-keywords-1): Revert inadvertently installed patch hunk.
[gnu-emacs] / lisp / progmodes / sh-script.el
index bc4ca59f96a586e786e9c80ad5d36ff3e18d50fe..d69229780b6bf87d0f6a275dbb9cefdc0eb6511c 100644 (file)
@@ -1,7 +1,7 @@
 ;;; sh-script.el --- shell-script editing commands for Emacs
 
-;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2003, 2004, 2005
-;;  Free Software Foundation, Inc.
+;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2003, 2004, 2005,
+;;  2006  Free Software Foundation, Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Version: 2.0f
@@ -356,6 +356,7 @@ the car and cdr are the same symbol.")
 
 (defvar sh-shell (sh-canonicalize-shell (file-name-nondirectory sh-shell-file))
   "The shell being programmed.  This is set by \\[sh-set-shell].")
+;;;###autoload(put 'sh-shell 'safe-local-variable 'symbolp)
 
 (defvar sh-mode-abbrev-table nil)
 
@@ -813,6 +814,18 @@ See `sh-feature'.")
      (:weight bold)))
   "Face to show a here-document"
   :group 'sh-indentation)
+
+;; These colours are probably icky.  It's just a placeholder though.
+(defface sh-quoted-exec
+  '((((class color) (background dark))
+     (:foreground "salmon"))
+    (((class color) (background light))
+     (:foreground "magenta"))
+    (t
+     (:weight bold)))
+  "Face to show quoted execs like ``"
+  :group 'sh-indentation)
+
 ;; backward-compatibility alias
 (put 'sh-heredoc-face 'face-alias 'sh-heredoc)
 (defvar sh-heredoc-face 'sh-heredoc)
@@ -832,7 +845,7 @@ See `sh-feature'.")
          font-lock-variable-name-face))
 
     (rc sh-append es)
-
+    (bash sh-append shell ("\\$(\\(\\sw+\\)" (1 'sh-quoted-exec t) ))
     (sh sh-append shell
        ;; Variable names.
        ("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2
@@ -966,6 +979,56 @@ Point is at the beginning of the next line."
   ;; This looks silly, but it's because `sh-here-doc-re' keeps changing.
   (re-search-forward sh-here-doc-re limit t))
 
+(defun sh-quoted-subshell (limit)
+  "Search for a subshell embedded in a string. Find all the unescaped
+\" characters within said subshell, remembering that subshells can nest."
+  ;; FIXME: This can (and often does) match multiple lines, yet it makes no
+  ;; effort to handle multiline cases correctly, so it ends up being
+  ;; rather flakey.
+  (if (re-search-forward "\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(\\\\\\\\\\)*\\)?\\(\\$(\\|`\\)" limit t)
+      ;; bingo we have a $( or a ` inside a ""
+      (let ((char (char-after (point)))
+            (continue t)
+            (pos (point))
+            (data nil)    ;; value to put into match-data (and return)
+            (last nil)    ;; last char seen
+            (bq  (equal (match-string 1) "`")) ;; ` state flip-flop
+            (seen nil)    ;; list of important positions
+            (nest 1))     ;; subshell nesting level
+        (while (and continue char (<= pos limit))
+          ;; unescaped " inside a $( ... ) construct.
+          ;; state machine time...
+          ;; \ => ignore next char;
+          ;; ` => increase or decrease nesting level based on bq flag
+          ;; ) [where nesting > 0] => decrease nesting
+          ;; ( [where nesting > 0] => increase nesting
+          ;; ( [preceeded by $ ]   => increase nesting
+          ;; " [nesting <= 0 ]     => terminate, we're done.
+          ;; " [nesting >  0 ]     => remember this, it's not a proper "
+          ;; FIXME: don't count parens that appear within quotes.
+          (cond
+           ((eq ?\\ last) nil)
+           ((eq ?\` char) (setq nest (+ nest (if bq -1 1)) bq (not bq)))
+           ((and (> nest 0) (eq ?\) char))   (setq nest (1- nest)))
+           ((and (eq ?$ last) (eq ?\( char)) (setq nest (1+ nest)))
+           ((and (> nest 0) (eq ?\( char))   (setq nest (1+ nest)))
+           ((eq char ?\")
+            (if (>= 0 nest) (setq continue nil) (push pos seen))))
+          ;;(message "POS: %d [%d]" pos nest)
+          (setq last char
+                pos  (1+ pos)
+                char (char-after pos)) )
+        ;; FIXME: why construct a costly match data to pass to
+        ;; sh-apply-quoted-subshell rather than apply the highlight
+        ;; directly here?  -- Stef
+        (when seen
+          ;;(message "SEEN: %S" seen)
+          (setq data (list (current-buffer)))
+          (dolist(P seen)
+            (setq data (cons P (cons (1+ P) data))))
+          (store-match-data data))
+        data) ))
+
 (defun sh-is-quoted-p (pos)
   (and (eq (char-before pos) ?\\)
        (not (sh-is-quoted-p (1- pos)))))
@@ -986,20 +1049,40 @@ Point is at the beginning of the next line."
                  (when (memq (char-before) '(?\" ?\'))
                    (condition-case nil (progn (backward-sexp 1) t)
                      (error nil)))))
-         (forward-comment (- (point-max)))
+          (while (progn
+                   (forward-comment (- (point-max)))
+                   ;; Maybe we've bumped into an escaped newline.
+                   (sh-is-quoted-p (point)))
+            (backward-char 1))
          (when (eq (char-before) ?|)
            (backward-char 1) t)))
     (when (save-excursion (backward-char 2) (looking-at ";;\\|in"))
       sh-st-punc)))
 
+(defun sh-apply-quoted-subshell ()
+  "Apply the `sh-st-punc' syntax to all the matches in `match-data'.
+This is used to flag quote characters in subshell constructs inside strings
+\(which should therefore not be treated as normal quote characters\)"
+  (let ((m (match-data)) a b)
+    (while m
+      (setq a (car  m)
+            b (cadr m)
+            m (cddr m))
+      (put-text-property a b 'syntax-table sh-st-punc))) sh-st-punc)
+
 (defconst sh-font-lock-syntactic-keywords
   ;; A `#' begins a comment when it is unquoted and at the beginning of a
   ;; word.  In the shell, words are separated by metacharacters.
   ;; The list of special chars is taken from the single-unix spec
   ;; of the shell command language (under `quoting') but with `$' removed.
   `(("[^|&;<>()`\\\"' \t\n]\\(#+\\)" 1 ,sh-st-symbol)
+    ;; In a '...' the backslash is not escaping.
+    ("\\(\\\\\\)'" 1 ,sh-st-punc)
     ;; Make sure $@ and @? are correctly recognized as sexps.
     ("\\$\\([?@]\\)" 1 ,sh-st-symbol)
+    ;; highlight (possibly nested) subshells inside "" quoted regions correctly.
+    (sh-quoted-subshell
+     (1 (sh-apply-quoted-subshell) t t))
     ;; Find HEREDOC starters and add a corresponding rule for the ender.
     (sh-font-lock-here-doc
      (2 (sh-font-lock-open-heredoc
@@ -1012,11 +1095,12 @@ Point is at the beginning of the next line."
     (")" 0 (sh-font-lock-paren (match-beginning 0)))))
 
 (defun sh-font-lock-syntactic-face-function (state)
-  (if (nth 3 state)
-      (if (char-valid-p (nth 3 state))
-         font-lock-string-face
-       sh-heredoc-face)
-    font-lock-comment-face))
+  (let ((q (nth 3 state)))
+    (if q
+        (if (char-valid-p q)
+            (if (eq q ?\`) 'sh-quoted-exec font-lock-string-face)
+          sh-heredoc-face)
+      font-lock-comment-face)))
 
 (defgroup sh-indentation nil
   "Variables controlling indentation in shell scripts.
@@ -1330,7 +1414,7 @@ shell-specific features.
 The default style of this mode is that of Rosenblatt's Korn shell book.
 The syntax of the statements varies with the shell being used.  The
 following commands are available, based on the current shell's syntax:
-
+\\<sh-mode-map>
 \\[sh-case]     case statement
 \\[sh-for]      for loop
 \\[sh-function]         function definition
@@ -1383,11 +1467,11 @@ with your script for an edit-interpret-debug cycle."
   (make-local-variable 'sh-shell-file)
   (make-local-variable 'sh-shell)
   (make-local-variable 'skeleton-pair-alist)
-  (make-local-variable 'skeleton-pair-filter)
+  (make-local-variable 'skeleton-pair-filter-function)
   (make-local-variable 'comint-dynamic-complete-functions)
   (make-local-variable 'comint-prompt-regexp)
   (make-local-variable 'font-lock-defaults)
-  (make-local-variable 'skeleton-filter)
+  (make-local-variable 'skeleton-filter-function)
   (make-local-variable 'skeleton-newline-indent-rigidly)
   (make-local-variable 'sh-shell-variables)
   (make-local-variable 'sh-shell-variables-initialized)
@@ -1415,24 +1499,36 @@ with your script for an edit-interpret-debug cycle."
          (font-lock-syntactic-face-function
           . sh-font-lock-syntactic-face-function))
        skeleton-pair-alist '((?` _ ?`))
-       skeleton-pair-filter 'sh-quoted-p
+       skeleton-pair-filter-function 'sh-quoted-p
        skeleton-further-elements '((< '(- (min sh-indentation
                                                (current-column)))))
-       skeleton-filter 'sh-feature
+       skeleton-filter-function 'sh-feature
        skeleton-newline-indent-rigidly t
        sh-indent-supported-here nil)
   (set (make-local-variable 'parse-sexp-ignore-comments) t)
   ;; Parse or insert magic number for exec, and set all variables depending
   ;; on the shell thus determined.
-  (let ((interpreter
-        (save-excursion
-          (goto-char (point-min))
-          (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))
-                 "rpm")))))
-    (sh-set-shell (or interpreter sh-shell-file) nil nil))
+  (sh-set-shell
+   (cond ((save-excursion
+            (goto-char (point-min))
+            (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)"))
+          (match-string 2))
+         ((not buffer-file-name)
+          sh-shell-file)
+         ;; Checks that use `buffer-file-name' follow.
+         ((string-match "\\.m?spec\\'" buffer-file-name)
+          "rpm")
+         ((string-match "[.]sh\\>" buffer-file-name)
+          "sh")
+         ((string-match "[.]bash\\>" buffer-file-name)
+          "bash")
+         ((string-match "[.]ksh\\>" buffer-file-name)
+          "ksh")
+         ((string-match "[.]csh\\>" buffer-file-name)
+          "csh")
+         (t
+          sh-shell-file))
+   nil nil)
   (run-mode-hooks 'sh-mode-hook))
 
 ;;;###autoload