]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/sh-script.el
Merge from emacs-23
[gnu-emacs] / lisp / progmodes / sh-script.el
index d463f299094af9c04dfc30365bdf313a2aaee8ee..adf3f9d1a5c4f684e0c2ab2db42936b95af238c1 100644 (file)
@@ -361,8 +361,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 ())
 
 
@@ -411,11 +409,7 @@ the car and cdr are the same symbol.")
     (modify-syntax-entry (pop list) (pop list) table))
   table)
 
-(defvar sh-mode-syntax-table nil
-  "The syntax table to use for Shell-Script mode.
-This is buffer-local in every such buffer.")
-
-(defvar sh-mode-default-syntax-table
+(defvar sh-mode-syntax-table
   (sh-mode-syntax-table ()
        ?\# "<"
        ?\n ">#"
@@ -436,7 +430,8 @@ This is buffer-local in every such buffer.")
        ?= "."
        ?< "."
        ?> ".")
-  "Default syntax table for shell mode.")
+  "The syntax table to use for Shell-Script mode.
+This is buffer-local in every such buffer.")
 
 (defvar sh-mode-syntax-table-input
   '((sh . nil))
@@ -568,19 +563,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/(( ... ))
@@ -611,7 +593,7 @@ sign.  See `sh-feature'."
 (defvar sh-header-marker nil
   "When non-nil is the end of header for prepending by \\[sh-execute-region].
 That command is also used for setting this variable.")
-
+(make-variable-buffer-local 'sh-header-marker)
 
 (defcustom sh-beginning-of-command
   "\\([;({`|&]\\|\\`\\|[^\\]\n\\)[ \t]*\\([/~[:alnum:]:]\\)"
@@ -779,7 +761,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")
@@ -942,7 +924,6 @@ 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
@@ -960,7 +941,7 @@ See `sh-feature'.")
 (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)
+(defun sh-font-lock-close-heredoc (bol eof indented eol)
   "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) ""))
@@ -974,6 +955,8 @@ If non-nil INDENTED indicates that the EOF was indented."
         (ere (concat "^" (if indented "[ \t]*") eof-re "\n"))
         (start (save-excursion
                  (goto-char bol)
+                  ;; FIXME: will incorrectly find a <<EOF embedded inside
+                  ;; the heredoc.
                  (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.
@@ -993,7 +976,7 @@ If non-nil INDENTED indicates that the EOF was indented."
                     (sh-in-comment-or-string (point)))))
          ;; No <<EOF2 found after our <<.
          (= (point) start)))
-      sh-here-doc-syntax)
+      (put-text-property eol (1+ eol) 'syntax-table sh-here-doc-syntax))
      ((not (or start (save-excursion (re-search-forward sre nil t))))
       ;; There's no <<EOF either before or after us,
       ;; so we should remove ourselves from font-lock's keywords.
@@ -1003,7 +986,7 @@ If non-nil INDENTED indicates that the EOF was indented."
                    (regexp-opt sh-here-doc-markers t) "\\(\n\\)"))
       nil))))
 
-(defun sh-font-lock-open-heredoc (start string)
+(defun sh-font-lock-open-heredoc (start string eol)
   "Determine the syntax of the \\n after a <<EOF.
 START is the position of <<.
 STRING is the actual word used as delimiter (e.g. \"EOF\").
@@ -1033,13 +1016,8 @@ Point is at the beginning of the next line."
           ;; 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 (point) 'syntax-multiline t)))
+    (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.
@@ -1048,9 +1026,7 @@ 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.
@@ -1085,8 +1061,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)
@@ -1125,7 +1100,7 @@ subshells can nest."
     (when (progn (backward-char 2)
                  (if (> start (line-end-position))
                      (put-text-property (point) (1+ start)
-                                        'font-lock-multiline t))
+                                        '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
@@ -1139,40 +1114,44 @@ subshells can nest."
       sh-st-punc
     nil))
 
-(defun sh-font-lock-flush-syntax-ppss-cache (limit)
-  ;; This should probably be a standard function provided by font-lock.el
-  ;; (or syntax.el).
-  (syntax-ppss-flush-cache (point))
-  (goto-char limit)
-  nil)
-
-(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-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))
-    ;; 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)))
+(defun sh-syntax-propertize-function (start end)
+  (goto-char start)
+  (while (prog1
+             (re-search-forward sh-here-doc-re end 'move)
+           (save-excursion
+             (save-match-data
+               (funcall
+                (syntax-propertize-rules
+                 ;; 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)))
+                 ;; Make sure $@ and $? are correctly recognized as sexps.
+                 ("\\$\\([?@]\\)" (1 "_"))
+                 ;; Distinguish the special close-paren in `case'.
+                 (")" (0 (sh-font-lock-paren (match-beginning 0))))
+                 ;; Highlight (possibly nested) subshells inside "" quoted
+                 ;; regions correctly.
+                 ("\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)"
+                  (1 (ignore
+                      ;; Save excursion because we want to also apply other
+                      ;; syntax-propertize rules within the affected region.
+                      (save-excursion
+                        (sh-font-lock-quoted-subshell end))))))
+                (prog1 start (setq start (point))) (point)))))
+    (if (match-beginning 2)
+        ;; FIXME: actually, once we see an heredoc opener, we should just
+        ;; search for its ender without propertizing anything in it.
+        (sh-font-lock-open-heredoc
+        (match-beginning 0) (match-string 1) (match-beginning 2))
+      (sh-font-lock-close-heredoc
+       (match-beginning 0) (match-string 4)
+       (and (match-beginning 3) (/= (match-beginning 3) (match-end 3)))
+       (match-beginning 5)))))
 
 (defun sh-font-lock-syntactic-face-function (state)
   (let ((q (nth 3 state)))
@@ -1480,7 +1459,7 @@ frequently editing existing scripts with different styles.")
 ;; mode-command and utility functions
 
 ;;;###autoload
-(defun sh-mode ()
+(define-derived-mode sh-mode prog-mode "Shell-script"
   "Major mode for editing shell scripts.
 This mode works for many shells, since they all have roughly the same syntax,
 as far as commands, arguments, variables, pipes, comments etc. are concerned.
@@ -1533,62 +1512,44 @@ indicate what shell it is use `sh-alias-alist' to translate.
 
 If your shell gives error messages with line numbers, you can use \\[executable-interpret]
 with your script for an edit-interpret-debug cycle."
-  (interactive)
-  (kill-all-local-variables)
-  (setq major-mode 'sh-mode
-       mode-name "Shell-script")
-  (use-local-map sh-mode-map)
-  (make-local-variable 'skeleton-end-hook)
-  (make-local-variable 'paragraph-start)
-  (make-local-variable 'paragraph-separate)
-  (make-local-variable 'comment-start)
-  (make-local-variable 'comment-start-skip)
-  (make-local-variable 'require-final-newline)
-  (make-local-variable 'sh-header-marker)
   (make-local-variable 'sh-shell-file)
   (make-local-variable 'sh-shell)
-  (make-local-variable 'skeleton-pair-alist)
-  (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-function)
-  (make-local-variable 'skeleton-newline-indent-rigidly)
-  (make-local-variable 'sh-shell-variables)
-  (make-local-variable 'sh-shell-variables-initialized)
-  (make-local-variable 'imenu-generic-expression)
-  (make-local-variable 'sh-indent-supported-here)
-  (make-local-variable 'skeleton-pair-default-alist)
-  (setq skeleton-pair-default-alist sh-skeleton-pair-default-alist)
-  (setq skeleton-end-hook (lambda ()
-                           (or (eolp) (newline) (indent-relative)))
-       paragraph-start (concat page-delimiter "\\|$")
-       paragraph-separate paragraph-start
-       comment-start "# "
-       comment-start-skip "#+[\t ]*"
-       local-abbrev-table sh-mode-abbrev-table
-       comint-dynamic-complete-functions sh-dynamic-complete-functions
-       ;; we can't look if previous line ended with `\'
-       comint-prompt-regexp "^[ \t]*"
-       imenu-case-fold-search nil
-       font-lock-defaults
-       `((sh-font-lock-keywords
-          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))
-       skeleton-pair-alist '((?` _ ?`))
-       skeleton-pair-filter-function 'sh-quoted-p
-       skeleton-further-elements '((< '(- (min sh-indentation
-                                               (current-column)))))
-       skeleton-filter-function 'sh-feature
-       skeleton-newline-indent-rigidly t
-       sh-indent-supported-here nil)
+
+  (set (make-local-variable 'skeleton-pair-default-alist)
+       sh-skeleton-pair-default-alist)
+  (set (make-local-variable 'skeleton-end-hook)
+       (lambda () (or (eolp) (newline) (indent-relative))))
+
+  (set (make-local-variable 'paragraph-start) (concat page-delimiter "\\|$"))
+  (set (make-local-variable 'paragraph-separate) paragraph-start)
+  (set (make-local-variable 'comment-start) "# ")
+  (set (make-local-variable 'comment-start-skip) "#+[\t ]*")
+  (set (make-local-variable 'local-abbrev-table) sh-mode-abbrev-table)
+  (set (make-local-variable 'comint-dynamic-complete-functions)
+       sh-dynamic-complete-functions)
+  ;; we can't look if previous line ended with `\'
+  (set (make-local-variable 'comint-prompt-regexp) "^[ \t]*")
+  (set (make-local-variable 'imenu-case-fold-search) nil)
+  (set (make-local-variable 'font-lock-defaults)
+       `((sh-font-lock-keywords
+          sh-font-lock-keywords-1 sh-font-lock-keywords-2)
+         nil nil
+         ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil
+         (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)
+       '((< '(- (min sh-indentation (current-column))))))
+  (set (make-local-variable 'skeleton-filter-function) 'sh-feature)
+  (set (make-local-variable 'skeleton-newline-indent-rigidly) t)
+  (set (make-local-variable 'sh-indent-supported-here) nil)
   (set (make-local-variable 'defun-prompt-regexp)
        (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)"))
-  (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.
   (sh-set-shell
@@ -1613,8 +1574,7 @@ with your script for an edit-interpret-debug cycle."
           "sh")
          (t
           sh-shell-file))
-   nil nil)
-  (run-mode-hooks 'sh-mode-hook))
+   nil nil))
 
 ;;;###autoload
 (defalias 'shell-script-mode 'sh-mode)
@@ -1741,23 +1701,18 @@ 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)
-       (setq require-final-newline mode-require-final-newline)))
-  (setq
-       mode-line-process (format "[%s]" sh-shell)
-       sh-shell-variables nil
-       sh-shell-variables-initialized nil
-       imenu-generic-expression (sh-feature sh-imenu-generic-expression))
-  (make-local-variable 'sh-mode-syntax-table)
+  (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)
+  (set (make-local-variable 'imenu-generic-expression)
+       (sh-feature sh-imenu-generic-expression))
   (let ((tem (sh-feature sh-mode-syntax-table-input)))
-    (setq sh-mode-syntax-table
-         (if tem (apply 'sh-mode-syntax-table tem)
-           sh-mode-default-syntax-table)))
-  (set-syntax-table sh-mode-syntax-table)
+    (when tem
+      (set (make-local-variable 'sh-mode-syntax-table)
+           (apply 'sh-mode-syntax-table tem))
+      (set-syntax-table sh-mode-syntax-table)))
   (dolist (var (sh-feature sh-variables))
     (sh-remember-variable var))
-  (make-local-variable 'indent-line-function)
   (if (setq sh-indent-supported-here (sh-feature sh-indent-supported))
       (progn
        (message "Setting up indent for shell type %s" sh-shell)
@@ -1770,7 +1725,7 @@ Calls the value of `sh-set-shell-hook' if set."
        (message "setting up indent stuff")
        ;; sh-mode has already made indent-line-function local
        ;; but do it in case this is called before that.
-       (setq indent-line-function 'sh-indent-line)
+       (set (make-local-variable 'indent-line-function) 'sh-indent-line)
        (if sh-make-vars-local
            (sh-make-vars-local))
        (message "Indentation setup for shell type %s" sh-shell))
@@ -2162,11 +2117,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)))
@@ -2233,10 +2184,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 "#")
@@ -3469,20 +3419,15 @@ CODE can be nil, t or `lambda'.
 nil means to return the best completion of STRING, or nil if there is none.
 t means to return a list of all possible completions of STRING.
 `lambda' means to return t if STRING is a valid completion as it stands."
-  (let ((sh-shell-variables
+  (let ((vars
         (with-current-buffer sh-add-buffer
           (or sh-shell-variables-initialized
               (sh-shell-initialize-variables))
           (nconc (mapcar (lambda (var)
-                           (let ((name
-                                  (substring var 0 (string-match "=" var))))
-                             (cons name name)))
+                            (substring var 0 (string-match "=" var)))
                          process-environment)
                  sh-shell-variables))))
-    (case code
-      ((nil) (try-completion string sh-shell-variables predicate))
-      (lambda (test-completion string sh-shell-variables predicate))
-      (t (all-completions string sh-shell-variables predicate)))))
+    (complete-with-action code vars string predicate)))
 
 (defun sh-add (var delta)
   "Insert an addition of VAR and prefix DELTA for Bourne (type) shell."
@@ -3872,5 +3817,4 @@ shell command and conveniently use this command."
 
 (provide 'sh-script)
 
-;; arch-tag: eccd8b72-f337-4fc2-ae86-18155a69d937
 ;;; sh-script.el ends here