]> 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 51291d717b6a7168afac595440a40e745cef0f9d..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)))))
@@ -996,6 +1059,17 @@ Point is at the beginning of the next line."
     (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.
@@ -1006,6 +1080,9 @@ Point is at the beginning of the next line."
     ("\\(\\\\\\)'" 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
@@ -1018,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.
@@ -1389,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)
@@ -1421,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