;;; sh-script.el --- shell-script editing commands for Emacs
-;; Copyright (C) 1993, 94, 95, 96, 97, 1999, 2001, 03, 2004
+;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 1999, 2001, 2003, 2004, 2005
;; Free Software Foundation, Inc.
;; Author: Daniel Pfeiffer <occitan@esperanto.org>
?\" "\"\""
?\' "\"'"
?\` "\"`"
+ ;; ?$ might also have a ". p" syntax. Both "'" and ". p" seem
+ ;; to work fine. This is needed so that dabbrev-expand
+ ;; $VARNAME works.
+ ?$ "'"
?! "_"
?% "_"
?: "_"
(define-key map "\C-c=" 'sh-set-indent)
(define-key map "\C-c<" 'sh-learn-line-indent)
(define-key map "\C-c>" 'sh-learn-buffer-indent)
+ (define-key map "\C-c\C-\\" 'sh-backslash-region)
(define-key map "=" 'sh-assignment)
(define-key map "\C-c+" 'sh-add)
(defcustom sh-require-final-newline
'((csh . t)
- (pdksh . t)
- (rc . require-final-newline)
- (sh . require-final-newline))
+ (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)
-(defvar sh-here-document-word "EOF"
+(defcustom sh-here-document-word "EOF"
"Word to delimit here documents.
-If the first character of this string is \"-\", this character will
-be removed from the string when it is used to close the here document.
-This convention is used by the Bash shell, for example, to indicate
-that leading tabs inside the here document should be ignored.
-Note that Emacs currently has no support for indenting inside here
-documents - you must insert literal tabs by hand.")
+If the first character of this string is \"-\", this is taken as
+part of the redirection operator, rather than part of the
+word (that is, \"<<-\" instead of \"<<\"). This is a feature
+used by some shells (for example Bash) to indicate that leading
+tabs inside the here document should be ignored. In this case,
+Emacs indents the initial body and end of the here document with
+tabs, to the same level as the start (note that apart from this
+there is no support for indentation of here documents). This
+will only work correctly if `sh-basic-offset' is a multiple of
+`tab-width'.
+
+Any quote characters or leading whitespace in the word are
+removed when closing the here document."
+ :type 'string
+ :group 'sh-script)
+
(defvar sh-test
'((sh "[ ]" . 3)
;; but it *did* have an asterisk in the docstring!
(defcustom sh-builtins
'((bash sh-append posix
- "." "alias" "bg" "bind" "builtin" "compgen" "complete"
+ "." "alias" "bg" "bind" "builtin" "caller" "compgen" "complete"
"declare" "dirs" "disown" "enable" "fc" "fg" "help" "history"
"jobs" "kill" "let" "local" "popd" "printf" "pushd" "shopt"
"source" "suspend" "typeset" "unalias")
\f
;; Font-Lock support
-(defface sh-heredoc-face
- '((((class color)
+(defface sh-heredoc
+ '((((min-colors 88) (class color)
+ (background dark))
+ (:foreground "yellow1" :weight bold))
+ (((class color)
(background dark))
(:foreground "yellow" :weight bold))
(((class color)
(:weight bold)))
"Face to show a here-document"
:group 'sh-indentation)
-(defvar sh-heredoc-face 'sh-heredoc-face)
+;; backward-compatibility alias
+(put 'sh-heredoc-face 'face-alias 'sh-heredoc)
+(defvar sh-heredoc-face 'sh-heredoc)
+(defface sh-escaped-newline '((t :inherit font-lock-string-face))
+ "Face used for (non-escaped) backslash at end of a line in Shell-script mode."
+ :group 'sh-script
+ :version "22.1")
(defvar sh-font-lock-keywords
'((csh sh-append shell
;; Function names.
("^\\(\\sw+\\)[ \t]*(" 1 font-lock-function-name-face)
("\\<\\(function\\)\\>[ \t]*\\(\\sw+\\)?"
- (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t)))
+ (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t))
+ ("\\(?:^\\s *\\|[[();&|]\\s *\\|\\(?:\\s +-[ao]\\|if\\|else\\|then\\|while\\|do\\)\\s +\\)\\(!\\)"
+ 1 font-lock-negation-char-face))
;; The next entry is only used for defining the others
(shell sh-append executable-font-lock-keywords
;; Using font-lock-string-face here confuses sh-get-indent-info.
- ("\\\\$" 0 font-lock-warning-face)
+ ("\\(^\\|[^\\]\\)\\(\\\\\\\\\\)*\\(\\\\\\)$" 3 'sh-escaped-newline)
("\\\\[^A-Za-z0-9]" 0 font-lock-string-face)
("\\${?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\|[$*_]\\)" 1
font-lock-variable-name-face))
(defconst sh-st-symbol (string-to-syntax "_"))
(defconst sh-here-doc-syntax (string-to-syntax "|")) ;; generic string
-(defconst sh-here-doc-open-re "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\|\\s_\\)+\\).*\\(\n\\)")
+(defconst sh-here-doc-open-re "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\)+\\).*\\(\n\\)")
(defvar sh-here-doc-markers nil)
(make-variable-buffer-local 'sh-here-doc-markers)
:group 'sh-indentation)
(defcustom sh-popup-occur-buffer nil
- "*Controls when `sh-learn-buffer-indent' pops the *indent* buffer.
+ "*Controls when `sh-learn-buffer-indent' pops the `*indent*' buffer.
If t it is always shown. If nil, it is shown only when there
are conflicts."
:type '(choice
(defcustom sh-basic-offset 4
"*The default indentation increment.
-This value is used for the + and - symbols in an indentation variable."
+This value is used for the `+' and `-' symbols in an indentation variable."
:type 'integer
:group 'sh-indentation)
:menu-tag "/ Indent left half sh-basic-offset")))
(defcustom sh-indent-for-else 0
- "*How much to indent an else relative to an if. Usually 0."
+ "*How much to indent an `else' relative to its `if'. Usually 0."
:type `(choice
(integer :menu-tag "A number (positive=>indent right)"
:tag "A number")
sh-symbol-list))
(defcustom sh-indent-for-fi 0
- "*How much to indent a fi relative to an if. Usually 0."
+ "*How much to indent a `fi' relative to its `if'. Usually 0."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
-(defcustom sh-indent-for-done '0
- "*How much to indent a done relative to its matching stmt. Usually 0."
+(defcustom sh-indent-for-done 0
+ "*How much to indent a `done' relative to its matching stmt. Usually 0."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
(defcustom sh-indent-after-else '+
- "*How much to indent a statement after an else statement."
+ "*How much to indent a statement after an `else' statement."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
(defcustom sh-indent-after-if '+
- "*How much to indent a statement after an if statement.
-This includes lines after else and elif statements, too, but
-does not affect then else elif or fi statements themselves."
+ "*How much to indent a statement after an `if' statement.
+This includes lines after `else' and `elif' statements, too, but
+does not affect the `else', `elif' or `fi' statements themselves."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
(defcustom sh-indent-for-then 0
- "*How much to indent a then relative to an if."
+ "*How much to indent a `then' relative to its `if'."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
-(defcustom sh-indent-for-do '*
- "*How much to indent a do statement.
-This is relative to the statement before the do, i.e. the
-while until or for statement."
+(defcustom sh-indent-for-do 0
+ "*How much to indent a `do' statement.
+This is relative to the statement before the `do', typically a
+`while', `until', `for', `repeat' or `select' statement."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
-(defcustom sh-indent-after-do '*
- "*How much to indent a line after a do statement.
-This is used when the do is the first word of the line.
-This is relative to the statement before the do, e.g. a
-while for repeat or select statement."
+(defcustom sh-indent-after-do '+
+ "*How much to indent a line after a `do' statement.
+This is used when the `do' is the first word of the line.
+This is relative to the statement before the `do', typically a
+`while', `until', `for', `repeat' or `select' statement."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
(defcustom sh-indent-after-loop-construct '+
"*How much to indent a statement after a loop construct.
-This variable is used when the keyword \"do\" is on the same line as the
-loop statement (e.g. \"until\", \"while\" or \"for\").
-If the do is on a line by itself, then `sh-indent-after-do' is used instead."
+This variable is used when the keyword `do' is on the same line as the
+loop statement (e.g., `until', `while' or `for').
+If the `do' is on a line by itself, then `sh-indent-after-do' is used instead."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
(defcustom sh-indent-after-done 0
- "*How much to indent a statement after a \"done\" keyword.
-Normally this is 0, which aligns the \"done\" to the matching
+ "*How much to indent a statement after a `done' keyword.
+Normally this is 0, which aligns the `done' to the matching
looping construct line.
-Setting it non-zero allows you to have the \"do\" statement on a line
+Setting it non-zero allows you to have the `do' statement on a line
by itself and align the done under to do."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
(defcustom sh-indent-for-case-label '+
"*How much to indent a case label statement.
-This is relative to the line containing the case statement."
+This is relative to the line containing the `case' statement."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
(defcustom sh-indent-for-case-alt '++
"*How much to indent statements after the case label.
-This is relative to the line containing the case statement."
+This is relative to the line containing the `case' statement."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
(defcustom sh-indent-after-open '+
"*How much to indent after a line with an opening parenthesis or brace.
-For an open paren after a function `sh-indent-after-function' is used."
+For an open paren after a function, `sh-indent-after-function' is used."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
;; These 2 are for the rc shell:
(defcustom sh-indent-after-switch '+
- "*How much to indent a case statement relative to the switch statement.
+ "*How much to indent a `case' statement relative to the `switch' statement.
This is for the rc shell."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
(defcustom sh-indent-after-case '+
- "*How much to indent a statement relative to the case statement.
+ "*How much to indent a statement relative to the `case' statement.
This is for the rc shell."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
+(defcustom sh-backslash-column 48
+ "*Column in which `sh-backslash-region' inserts backslashes."
+ :type 'integer
+ :group 'sh)
+
+(defcustom sh-backslash-align t
+ "*If non-nil, `sh-backslash-region' will align backslashes."
+ :type 'boolean
+ :group 'sh)
+
;; Internal use - not designed to be changed by the user:
(defun sh-mkword-regexpr (word)
(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))
+ (string-match "\\.m?spec\\'" buffer-file-name))
"rpm")))))
(sh-set-shell (or interpreter sh-shell-file) nil nil))
- (run-hooks 'sh-mode-hook))
+ (run-mode-hooks 'sh-mode-hook))
;;;###autoload
(defalias 'shell-script-mode 'sh-mode)
(executable-set-magic shell (sh-feature sh-shell-arg)
no-query-flag insert-flag)))
(let ((tem (sh-feature sh-require-final-newline)))
- (unless (eq tem 'require-final-newline)
- (setq require-final-newline tem)))
+ (if (eq tem t)
+ (setq require-final-newline mode-require-final-newline)))
(setq
comment-start-skip "#+[\t ]*"
local-abbrev-table sh-mode-abbrev-table
(if (looking-at "[\"'`]")
(sh-safe-forward-sexp)
;; (> (skip-chars-forward "^ \t\n\"'`") 0)
- (> (skip-chars-forward "-_a-zA-Z\$0-9") 0)
+ (> (skip-chars-forward "-_a-zA-Z$0-9") 0)
))
(buffer-substring start (point))
))
(defun sh-var-value (var &optional ignore-error)
"Return the value of variable VAR, interpreting symbols.
It can also return t or nil.
-If an illegal value is found, throw an error unless Optional argument
+If an invalid value is found, throw an error unless Optional argument
IGNORE-ERROR is non-nil."
(let ((val (symbol-value var)))
(cond
(let ((sh-add-buffer (current-buffer)))
(list (completing-read "Variable: " 'sh-add-completer)
(prefix-numeric-value current-prefix-arg))))
- (insert (sh-feature '((bash . "$[ ")
+ (insert (sh-feature '((bash . "$(( ")
(ksh88 . "$(( ")
(posix . "$(( ")
(rc . "`{expr $")
(sh-remember-variable var)
(if (< delta 0) " - " " + ")
(number-to-string (abs delta))
- (sh-feature '((bash . " ]")
+ (sh-feature '((bash . " ))")
(ksh88 . " ))")
(posix . " ))")
(rc . "}")
"esac" >
\n "done"
> \n
- "shift " (sh-add "OPTIND" -1) \n))
+ "shift " (sh-add "OPTIND" -1) \n
+ "OPTIND=1" \n))
(match-string 1))))))
-
(defun sh-maybe-here-document (arg)
"Insert self. Without prefix, following unquoted `<' inserts here document.
The document is bounded by `sh-here-document-word'."
(save-excursion
(backward-char 2)
(sh-quoted-p))
- (progn
+ (let ((tabs (if (string-match "\\`-" sh-here-document-word)
+ (make-string (/ (current-indentation) tab-width) ?\t)
+ ""))
+ (delim (replace-regexp-in-string "['\"]" ""
+ sh-here-document-word)))
(insert sh-here-document-word)
(or (eolp) (looking-at "[ \t]") (insert ? ))
(end-of-line 1)
(while
(sh-quoted-p)
(end-of-line 2))
- (newline)
+ (insert ?\n tabs)
(save-excursion
- (insert ?\n (substring
- sh-here-document-word
- (if (string-match "^-" sh-here-document-word) 1 0)))))))
+ (insert ?\n tabs (replace-regexp-in-string
+ "\\`-?[ \t]*" "" delim))))))
\f
;; various other commands
(if (re-search-forward sh-end-of-command nil t)
(goto-char (match-end 1))))
+;; Backslashification. Stolen from make-mode.el.
+
+(defun sh-backslash-region (from to delete-flag)
+ "Insert, align, or delete end-of-line backslashes on the lines in the region.
+With no argument, inserts backslashes and aligns existing backslashes.
+With an argument, deletes the backslashes.
+
+This function does not modify the last line of the region if the region ends
+right at the start of the following line; it does not modify blank lines
+at the start of the region. So you can put the region around an entire
+shell command and conveniently use this command."
+ (interactive "r\nP")
+ (save-excursion
+ (goto-char from)
+ (let ((column sh-backslash-column)
+ (endmark (make-marker)))
+ (move-marker endmark to)
+ ;; Compute the smallest column number past the ends of all the lines.
+ (if sh-backslash-align
+ (progn
+ (if (not delete-flag)
+ (while (< (point) to)
+ (end-of-line)
+ (if (= (preceding-char) ?\\)
+ (progn (forward-char -1)
+ (skip-chars-backward " \t")))
+ (setq column (max column (1+ (current-column))))
+ (forward-line 1)))
+ ;; Adjust upward to a tab column, if that doesn't push
+ ;; past the margin.
+ (if (> (% column tab-width) 0)
+ (let ((adjusted (* (/ (+ column tab-width -1) tab-width)
+ tab-width)))
+ (if (< adjusted (window-width))
+ (setq column adjusted))))))
+ ;; Don't modify blank lines at start of region.
+ (goto-char from)
+ (while (and (< (point) endmark) (eolp))
+ (forward-line 1))
+ ;; Add or remove backslashes on all the lines.
+ (while (and (< (point) endmark)
+ ;; Don't backslashify the last line
+ ;; if the region ends right at the start of the next line.
+ (save-excursion
+ (forward-line 1)
+ (< (point) endmark)))
+ (if (not delete-flag)
+ (sh-append-backslash column)
+ (sh-delete-backslash))
+ (forward-line 1))
+ (move-marker endmark nil))))
+
+(defun sh-append-backslash (column)
+ (end-of-line)
+ ;; Note that "\\\\" is needed to get one backslash.
+ (if (= (preceding-char) ?\\)
+ (progn (forward-char -1)
+ (delete-horizontal-space)
+ (indent-to column (if sh-backslash-align nil 1)))
+ (indent-to column (if sh-backslash-align nil 1))
+ (insert "\\")))
+
+(defun sh-delete-backslash ()
+ (end-of-line)
+ (or (bolp)
+ (progn
+ (forward-char -1)
+ (if (looking-at "\\\\")
+ (delete-region (1+ (point))
+ (progn (skip-chars-backward " \t") (point)))))))
+
(provide 'sh-script)
-;;; arch-tag: eccd8b72-f337-4fc2-ae86-18155a69d937
+;; arch-tag: eccd8b72-f337-4fc2-ae86-18155a69d937
;;; sh-script.el ends here