X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/1c5f6018d55ea3c0e5430cde60624e2232d87bec..7be1c708c5abc7dea388d45454bd19bff07b7943:/lisp/progmodes/sh-script.el diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el index 5f4028af89..31a4fbaef4 100644 --- a/lisp/progmodes/sh-script.el +++ b/lisp/progmodes/sh-script.el @@ -1,7 +1,6 @@ ;;; sh-script.el --- shell-script editing commands for Emacs -;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2002, 2003, -;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. +;; Copyright (C) 1993-1997, 1999, 2001-2011 Free Software Foundation, Inc. ;; Author: Daniel Pfeiffer ;; Version: 2.0f @@ -361,8 +360,6 @@ the car and cdr are the same symbol.") "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) - (define-abbrev-table 'sh-mode-abbrev-table ()) @@ -565,19 +562,6 @@ This is buffer-local in every such buffer.") :type '(repeat function) :group 'sh-script) - -(defcustom sh-require-final-newline - '((csh . t) - (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) - (sexp :format "Evaluate: %v")))) - :group 'sh-script) - - (defcustom sh-assignment-regexp '((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=") ;; actually spaces are only supported in let/(( ... )) @@ -776,7 +760,7 @@ flow of control or syntax. See `sh-feature'." (shell "break" "case" "continue" "exec" "exit") (zsh sh-append bash - "select")) + "select" "foreach")) "List of keywords not in `sh-leading-keywords'. See `sh-feature'." :type '(repeat (cons (symbol :tag "Shell") @@ -939,68 +923,20 @@ See `sh-feature'.") ;; These are used for the syntax table stuff (derived from cperl-mode). ;; Note: parse-sexp-lookup-properties must be set to t for it to work. (defconst sh-st-punc (string-to-syntax ".")) -(defconst sh-st-symbol (string-to-syntax "_")) (defconst sh-here-doc-syntax (string-to-syntax "|")) ;; generic string -(defconst sh-escaped-line-re - ;; Should match until the real end-of-continued-line, but if that is not - ;; possible (because we bump into EOB or the search bound), then we should - ;; match until the search bound. - "\\(?:\\(?:.*[^\\\n]\\)?\\(?:\\\\\\\\\\)*\\\\\n\\)*.*") - -(defconst sh-here-doc-open-re - (concat "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\)+\\)" - sh-escaped-line-re "\\(\n\\)")) - -(defvar sh-here-doc-markers nil) -(make-variable-buffer-local 'sh-here-doc-markers) -(defvar sh-here-doc-re sh-here-doc-open-re) -(make-variable-buffer-local 'sh-here-doc-re) - -(defun sh-font-lock-close-heredoc (bol eof indented) - "Determine the syntax of the \\n after an EOF. -If non-nil INDENTED indicates that the EOF was indented." - (let* ((eof-re (if eof (regexp-quote eof) "")) - ;; A rough regexp that should find the opening <]" - sh-escaped-line-re - "\\)?\\s|")) - ;; A regexp that will find other EOFs. - (ere (concat "^" (if indented "[ \t]*") eof-re "\n")) - (start (save-excursion - (goto-char bol) - (re-search-backward (concat sre "\\|" ere) nil t)))) - ;; If subgroup 1 matched, we found an open-heredoc, otherwise we first - ;; found a close-heredoc which makes the current close-heredoc inoperant. - (cond - ((when (and start (match-end 1) - (not (and indented (= (match-beginning 1) (match-end 1)))) - (not (sh-in-comment-or-string (match-beginning 0)))) - ;; Make sure our `<<' is not the EOF1 of a `cat <" - (if (or (nth 5 ppss) (> (count-lines start (point)) 1)) - ;; If the sh-escaped-line-re part of sh-here-doc-re has matched + (put-text-property (1- eol) eol 'syntax-table '(12))) ;">" + (if (or (nth 5 ppss) (> (count-lines start eol) 1)) + ;; If the sh-escaped-line-re part of sh-here-doc-open-re has matched ;; several lines, make sure we refontify them together. ;; Furthermore, if (nth 5 ppss) is non-nil (i.e. the \n is ;; escaped), it means the right \n is actually further down. ;; Don't bother fixing it now, but place a multiline property so ;; that when jit-lock-context-* refontifies the rest of the ;; buffer, it also refontifies the current line with it. - (put-text-property start (point) 'font-lock-multiline t))) - sh-here-doc-syntax)) - -(defun sh-font-lock-here-doc (limit) - "Search for a heredoc marker." - ;; This looks silly, but it's because `sh-here-doc-re' keeps changing. - (re-search-forward sh-here-doc-re limit t)) + (put-text-property start (1+ eol) 'syntax-multiline t)) + (put-text-property eol (1+ eol) 'sh-here-doc-marker str) + (prog1 sh-here-doc-syntax + (goto-char (+ 2 start)))))) + +(defun sh-syntax-propertize-here-doc (end) + (let ((ppss (syntax-ppss))) + (when (eq t (nth 3 ppss)) + (let ((key (get-text-property (nth 8 ppss) 'sh-here-doc-marker)) + (case-fold-search nil)) + (when (re-search-forward + (concat "^\\([ \t]*\\)" (regexp-quote key) "\\(\n\\)") + end 'move) + (let ((eol (match-beginning 2))) + (put-text-property eol (1+ eol) + 'syntax-table sh-here-doc-syntax))))))) (defun sh-font-lock-quoted-subshell (limit) "Search for a subshell embedded in a string. @@ -1045,12 +985,9 @@ 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. - (when (and (re-search-forward "\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)" limit t) - ;; Make sure the " we matched is an opening quote. - (eq ?\" (nth 3 (syntax-ppss)))) + (when (eq ?\" (nth 3 (syntax-ppss))) ; Check we matched an opening quote. ;; bingo we have a $( or a ` inside a "" - (let ((char (char-after (point))) - ;; `state' can be: double-quote, backquote, code. + (let (;; `state' can be: double-quote, backquote, code. (state (if (eq (char-before) ?`) 'backquote 'code)) ;; Stacked states in the context. (states '(double-quote))) @@ -1082,8 +1019,7 @@ subshells can nest." (double-quote nil) (t (setq state (pop states))))) (t (error "Internal error in sh-font-lock-quoted-subshell"))) - (forward-char 1))) - t)) + (forward-char 1))))) (defun sh-is-quoted-p (pos) @@ -1091,44 +1027,52 @@ subshells can nest." (not (sh-is-quoted-p (1- pos))))) (defun sh-font-lock-paren (start) - (save-excursion - (goto-char start) - ;; Skip through all patterns - (while - (progn - (forward-comment (- (point-max))) - ;; Skip through one pattern - (while - (or (/= 0 (skip-syntax-backward "w_")) - (/= 0 (skip-chars-backward "?[]*@/\\")) - (and (sh-is-quoted-p (1- (point))) - (goto-char (- (point) 2))) - (when (memq (char-before) '(?\" ?\')) - (condition-case nil (progn (backward-sexp 1) t) - (error nil))))) - ;; Patterns can be preceded by an open-paren (Bug#1320). - (if (eq (char-before (point)) ?\() - (backward-char 1)) - (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))) - ;; FIXME: ";; esac )" is a case that looks like a case-pattern but it's - ;; really just a close paren after a case statement. I.e. if we skipped - ;; over `esac' just now, we're not looking at a case-pattern. - (when (progn (backward-char 2) - (if (> start (line-end-position)) - (put-text-property (point) (1+ start) - 'font-lock-multiline t)) - ;; FIXME: The `in' may just be a random argument to - ;; a normal command rather than the real `in' keyword. - ;; I.e. we should look back to try and find the - ;; corresponding `case'. - (looking-at ";;\\|in")) - sh-st-punc))) + (unless (nth 8 (syntax-ppss)) + (save-excursion + (goto-char start) + ;; Skip through all patterns + (while + (progn + (while + (progn + (forward-comment (- (point-max))) + (when (and (eolp) (sh-is-quoted-p (point))) + (forward-char -1) + t))) + ;; Skip through one pattern + (while + (or (/= 0 (skip-syntax-backward "w_")) + (/= 0 (skip-chars-backward "-$=?[]*@/\\\\")) + (and (sh-is-quoted-p (1- (point))) + (goto-char (- (point) 2))) + (when (memq (char-before) '(?\" ?\' ?\})) + (condition-case nil (progn (backward-sexp 1) t) + (error nil))))) + ;; Patterns can be preceded by an open-paren (Bug#1320). + (if (eq (char-before (point)) ?\() + (backward-char 1)) + (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 (progn (backward-char 2) + (if (> start (line-end-position)) + (put-text-property (point) (1+ start) + 'syntax-multiline t)) + ;; FIXME: The `in' may just be a random argument to + ;; a normal command rather than the real `in' keyword. + ;; I.e. we should look back to try and find the + ;; corresponding `case'. + (and (looking-at ";[;&]\\|\\_()`\\\"' \t\n]\\(#+\\)" 1 ,sh-st-symbol) +(defun sh-syntax-propertize-function (start end) + (goto-char start) + (sh-syntax-propertize-here-doc end) + (funcall + (syntax-propertize-rules + (sh-here-doc-open-re + (2 (sh-font-lock-open-heredoc + (match-beginning 0) (match-string 1) (match-beginning 2)))) + ("\\s|" (0 (prog1 nil (sh-syntax-propertize-here-doc end)))) + ;; 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 "_")) ;; In a '...' the backslash is not escaping. ("\\(\\\\\\)'" (1 (sh-font-lock-backslash-quote))) - ;; The previous rule uses syntax-ppss, but the subsequent rules may - ;; change the syntax, so we have to tell syntax-ppss that the states it - ;; has just computed will need to be recomputed. - (sh-font-lock-flush-syntax-ppss-cache) ;; Make sure $@ and $? are correctly recognized as sexps. - ("\\$\\([?@]\\)" 1 ,sh-st-symbol) - ;; Find HEREDOC starters and add a corresponding rule for the ender. - (sh-font-lock-here-doc - (2 (sh-font-lock-open-heredoc - (match-beginning 0) (match-string 1)) nil t) - (5 (sh-font-lock-close-heredoc - (match-beginning 0) (match-string 4) - (and (match-beginning 3) (/= (match-beginning 3) (match-end 3)))) - nil t)) + ("\\$\\([?@]\\)" (1 "_")) ;; Distinguish the special close-paren in `case'. - (")" 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-font-lock-quoted-subshell))) + (")" (0 (sh-font-lock-paren (match-beginning 0)))) + ;; Highlight (possibly nested) subshells inside "" quoted + ;; regions correctly. + ("\"\\(?:\\(?:[^\\\"]\\|\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)" + (1 (ignore + ;; Save excursion because we want to also apply other + ;; syntax-propertize rules within the affected region. + (if (nth 8 (syntax-ppss)) + (goto-char (1+ (match-beginning 0))) + (save-excursion + (sh-font-lock-quoted-subshell end))))))) + (point) end)) (defun sh-font-lock-syntactic-face-function (state) (let ((q (nth 3 state))) @@ -1269,7 +1211,7 @@ a number means align to that column, e.g. 0 means first column." ;; "For debugging: display message ARGS if variable SH-DEBUG is non-nil." ;; (if sh-debug ;; (apply 'message args))) -(defmacro sh-debug (&rest args)) +(defmacro sh-debug (&rest _args)) (defconst sh-symbol-list '((const :tag "+ " :value + @@ -1553,9 +1495,12 @@ with your script for an edit-interpret-debug cycle." sh-font-lock-keywords-1 sh-font-lock-keywords-2) nil nil ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil - (font-lock-syntactic-keywords . sh-font-lock-syntactic-keywords) (font-lock-syntactic-face-function . sh-font-lock-syntactic-face-function))) + (set (make-local-variable 'syntax-propertize-function) + #'sh-syntax-propertize-function) + (add-hook 'syntax-propertize-extend-region-functions + #'syntax-propertize-multiline 'append 'local) (set (make-local-variable 'skeleton-pair-alist) '((?` _ ?`))) (set (make-local-variable 'skeleton-pair-filter-function) 'sh-quoted-p) (set (make-local-variable 'skeleton-further-elements) @@ -1675,6 +1620,8 @@ This adds rules for comments and assignments." ("esac" sh-handle-this-esac sh-handle-prev-esac) (case-label nil sh-handle-after-case-label) ;; ??? (";;" nil sh-handle-prev-case-alt-end) ;; ??? + (";;&" nil sh-handle-prev-case-alt-end) ;Like ";;" with diff semantics. + (";&" nil sh-handle-prev-case-alt-end) ;Like ";;" with diff semantics. ("done" sh-handle-this-done sh-handle-prev-done) ("do" sh-handle-this-do sh-handle-prev-do)) @@ -1716,10 +1663,6 @@ Calls the value of `sh-set-shell-hook' if set." (setq sh-shell-file (executable-set-magic shell (sh-feature sh-shell-arg) no-query-flag insert-flag))) - (let ((tem (sh-feature sh-require-final-newline))) - (if (eq tem t) - (set (make-local-variable 'require-final-newline) - mode-require-final-newline))) (setq mode-line-process (format "[%s]" sh-shell)) (set (make-local-variable 'sh-shell-variables) nil) (set (make-local-variable 'sh-shell-variables-initialized) nil) @@ -2136,11 +2079,7 @@ Return new point if successful, nil if an error occurred." (defun sh-handle-prev-do () (cond ((save-restriction - (narrow-to-region - (point) - (save-excursion - (beginning-of-line) - (point))) + (narrow-to-region (point) (line-beginning-position)) (sh-goto-match-for-done)) (sh-debug "match for done found on THIS line") (list '(+ sh-indent-after-loop-construct))) @@ -2198,7 +2137,6 @@ STRING This is ignored for the purposes of calculating (save-excursion (let ((have-result nil) this-kw - start val (result nil) (align-point nil) @@ -2207,10 +2145,9 @@ STRING This is ignored for the purposes of calculating ;; Note: setting result to t means we are done and will return nil. ;;(This function never returns just t.) (cond - ((or (and (boundp 'font-lock-string-face) (not (bobp)) - (eq (get-text-property (1- (point)) 'face) - font-lock-string-face)) + ((or (nth 3 (syntax-ppss (point))) (eq (get-text-property (point) 'face) sh-heredoc-face)) + ;; String continuation -- don't indent (setq result t) (setq have-result t)) ((looking-at "\\s-*#") ; was (equal this-kw "#") @@ -2270,7 +2207,6 @@ STRING This is ignored for the purposes of calculating ;; We start off at beginning of this line. ;; Scan previous statements while this is <= ;; start of previous line. - (setq start (point)) ;; for debug only (goto-char prev-line-end) (setq x t) (while (and x (setq x (sh-prev-thing))) @@ -2521,7 +2457,7 @@ we go to the end of the previous line and do not check for continuations." (sh-prev-line nil) (line-beginning-position)))) (skip-chars-backward " \t;" min-point) - (if (looking-at "\\s-*;;") + (if (looking-at "\\s-*;[;&]") ;; (message "Found ;; !") ";;" (skip-chars-backward "^)}];\"'`({[" min-point) @@ -2675,7 +2611,7 @@ can be represented by a symbol then do so." If INFO is supplied it is used, else it is calculated from current line." (let ((ofs 0) (base-value 0) - elt a b var val) + elt a b val) (or info (setq info (sh-get-indent-info))) (when info @@ -3841,5 +3777,4 @@ shell command and conveniently use this command." (provide 'sh-script) -;; arch-tag: eccd8b72-f337-4fc2-ae86-18155a69d937 ;;; sh-script.el ends here