(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) ?\\)
("\\(\\\\\\)'" 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
(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)))
(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)
;;
(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 ()