]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
(sh-prev-thing): Remove (forward-char 1) now
[gnu-emacs] / lisp / progmodes / sh-script.el
index 48b34a993112552d768c78c95691485e295259cf..a15cc216f75ed2292b4e2fbe6091f841b52913ac 100644 (file)
@@ -980,54 +980,57 @@ Point is at the beginning of the next line."
   (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."
+  "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) ))
+  (when (and (re-search-forward "\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)" limit t)
+             ;; Make sure the " we matched is an opening quote.
+            (eq ?\" (nth 3 (syntax-ppss))))
+    ;; 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) ?\\)
@@ -1080,9 +1083,6 @@ This is used to flag quote characters in subshell constructs inside strings
     ("\\(\\\\\\)'" 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
@@ -1092,7 +1092,11 @@ This is used to flag quote characters in subshell constructs inside strings
          (and (match-beginning 3) (/= (match-beginning 3) (match-end 3))))
       nil t))
     ;; Distinguish the special close-paren in `case'.
-    (")" 0 (sh-font-lock-paren (match-beginning 0)))))
+    (")" 0 (sh-font-lock-paren (match-beginning 0)))
+    ;; highlight (possibly nested) subshells inside "" quoted regions correctly.
+    ;; This should be at the very end because it uses syntax-ppss.
+    (sh-quoted-subshell
+     (1 (sh-apply-quoted-subshell) t t))))
 
 (defun sh-font-lock-syntactic-face-function (state)
   (let ((q (nth 3 state)))
@@ -1561,7 +1565,7 @@ This adds rules for comments and assignments."
                         (regexp-opt (sh-feature sh-builtins) t)
                         "\\>")
                (2 font-lock-keyword-face nil t)
-               (4 font-lock-builtin-face))
+               (6 font-lock-builtin-face))
               ,@(sh-feature sh-font-lock-keywords-var-2)))
         (,(concat keywords "\\)\\>")
          2 font-lock-keyword-face)
@@ -2456,46 +2460,45 @@ we go to the end of the previous line and do not check for continuations."
   ;;
   (if (bolp)
       nil
-    (let (c min-point
-         (start (point)))
-      (save-restriction
-       (narrow-to-region
-       (if (sh-this-is-a-continuation)
-           (setq min-point (sh-prev-line nil))
-         (save-excursion
-           (beginning-of-line)
-           (setq min-point (point))))
-       (point))
-       (skip-chars-backward " \t;")
-       (unless (looking-at "\\s-*;;")
-       (skip-chars-backward "^)}];\"'`({[")
-       (setq c (char-before))))
-      (sh-debug "stopping at %d c is %s  start=%d min-point=%d"
-               (point) c start min-point)
-      (if (< (point) min-point)
-         (error "point %d < min-point %d" (point) min-point))
-      (cond
-       ((looking-at "\\s-*;;")
-       ;; (message "Found ;; !")
-       ";;")
-       ((or (eq c ?\n)
-           (eq c nil)
-           (eq c ?\;))
-       (save-excursion
-        ;; skip forward over white space newline and \ at eol
-        (skip-chars-forward " \t\n\\\\")
-        (sh-debug "Now at %d   start=%d" (point) start)
-        (if (>= (point) start)
-            (progn
-              (sh-debug "point: %d >= start: %d" (point) start)
-              nil)
-          (sh-get-word))
-        ))
-       (t
-       ;; c    -- return a string
-       (char-to-string c)
-       ))
-      )))
+    (let ((start (point))
+          (min-point (if (sh-this-is-a-continuation)
+                         (sh-prev-line nil)
+                       (line-beginning-position))))
+      (skip-chars-backward " \t;" min-point)
+      (if (looking-at "\\s-*;;")
+          ;; (message "Found ;; !")
+          ";;"
+        (skip-chars-backward "^)}];\"'`({[" min-point)
+        (let ((c (if (> (point) min-point) (char-before))))
+          (sh-debug "stopping at %d c is %s  start=%d min-point=%d"
+                    (point) c start min-point)
+          (if (not (memq c '(?\n nil ?\;)))
+              ;; c     -- return a string
+              (char-to-string c)
+            ;; Return the leading keyword of the "command" we supposedly
+            ;; skipped over.  Maybe we skipped too far (e.g. past a `do' or
+            ;; `then' that precedes the actual command), so check whether
+            ;; we're looking at such a keyword and if so, move back forward.
+            (let ((boundary (point))
+                  kwd next)
+              (while
+                  (progn
+                    ;; Skip forward over white space newline and \ at eol.
+                    (skip-chars-forward " \t\n\\\\" start)
+                    (if (>= (point) start)
+                        (progn
+                          (sh-debug "point: %d >= start: %d" (point) start)
+                          nil)
+                      (if next (setq boundary next))
+                      (sh-debug "Now at %d   start=%d" (point) start)
+                      (setq kwd (sh-get-word))
+                      (if (member kwd (sh-feature sh-leading-keywords))
+                          (progn
+                            (setq next (point))
+                            t)
+                        nil))))
+              (goto-char boundary)
+              kwd)))))))
 
 
 (defun sh-this-is-a-continuation ()