;;; sh-script.el --- shell-script editing commands for Emacs
-;; Copyright (C) 1993, 94, 95, 96, 97, 1999 by Free Software Foundation, Inc.
+;; Copyright (C) 1993, 94, 95, 96, 97, 1999, 2001, 2003
+;; Free Software Foundation, Inc.
;; Author: Daniel Pfeiffer <occitan@esperanto.org>
;; Version: 2.0f
;; ===========
;; Indentation for rc and es modes is very limited, but for Bourne shells
;; and its derivatives it is quite customizable.
-;;
+;;
;; The following description applies to sh and derived shells (bash,
;; zsh, ...).
-;;
+;;
;; There are various customization variables which allow tailoring to
;; a wide variety of styles. Most of these variables are named
;; sh-indent-for-XXX and sh-indent-after-XXX. For example.
;; sh-indent-after-if controls the indenting of a line following
;; an if statement, and sh-indent-for-fi controls the indentation
;; of the line containing the fi.
-;;
+;;
;; You can set each to a numeric value, but it is often more convenient
;; to a symbol such as `+' which uses the value of variable `sh-basic-offset'.
;; By changing this one variable you can increase or decrease how much
;; indentation there is. Valid symbols:
-;;
+;;
;; + Indent right by sh-basic-offset
;; - Indent left by sh-basic-offset
;; ++ Indent right twice sh-basic-offset
;; -- Indent left twice sh-basic-offset
;; * Indent right half sh-basic-offset
;; / Indent left half sh-basic-offset.
-;;
+;;
;; There are 4 commands to help set the indentation variables:
-;;
+;;
;; `sh-show-indent'
;; This shows what variable controls the indentation of the current
;; line and its value.
-;;
+;;
;; `sh-set-indent'
;; This allows you to set the value of the variable controlling the
;; current line's indentation. You can enter a number or one of a
;; or its negative, or half it, or twice it, etc. If you've used
;; cc-mode this should be familiar. If you forget which symbols are
;; valid simply press C-h at the prompt.
-;;
+;;
;; `sh-learn-line-indent'
;; Simply make the line look the way you want it, then invoke this
;; command. It will set the variable to the value that makes the line
;; indent like that. If called with a prefix argument then it will set
;; the value to one of the symbols if applicable.
-;;
+;;
;; `sh-learn-buffer-indent'
;; This is the deluxe function! It "learns" the whole buffer (use
;; narrowing if you want it to process only part). It outputs to a
;; pattern; if they don't it will be set to nil.
;; Whether `sh-basic-offset' is set is determined by variable
;; `sh-learn-basic-offset'.
-;;
+;;
;; Unfortunately, `sh-learn-buffer-indent' can take a long time to run
;; (e.g. if there are large case statements). Perhaps it does not make
;; sense to run it on large buffers: if lots of lines have different
;; *indent* buffer; if there is a consistent style then running
;; `sh-learn-buffer-indent' on a small region of the buffer should
;; suffice.
-;;
+;;
;; Saving indentation values
;; -------------------------
;; After you've learned the values in a buffer, how to you remember
;; would make this unnecessary; simply learn the values when you visit
;; the buffer.
;; You can do this automatically like this:
-; (add-hook 'sh-set-shell-hook 'sh-learn-buffer-indent)
-;;
-;; However... `sh-learn-buffer-indent' is extremely slow,
+;; (add-hook 'sh-set-shell-hook 'sh-learn-buffer-indent)
+;;
+;; However... `sh-learn-buffer-indent' is extremely slow,
;; especially on large-ish buffer. Also, if there are conflicts the
;; "last one wins" which may not produce the desired setting.
-;;
+;;
;; So...There is a minimal way of being able to save indentation values and
;; to reload them in another buffer or at another point in time.
-;;
+;;
;; Use `sh-name-style' to give a name to the indentation settings of
;; the current buffer.
;; Use `sh-load-style' to load indentation settings for the current
;; Use `sh-save-styles-to-buffer' to write all the styles to a buffer
;; in lisp code. You can then store it in a file and later use
;; `load-file' to load it.
-;;
+;;
;; Indentation variables - buffer local or global?
;; ----------------------------------------------
;; I think that often having them buffer-local makes sense,
;; especially if one is using `sh-learn-buffer-indent'. However, if
;; a user sets values using customization, these changes won't appear
;; to work if the variables are already local!
-;;
+;;
;; To get round this, there is a variable `sh-make-vars-local' and 2
;; functions: `sh-make-vars-local' and `sh-reset-indent-vars-to-global-values'.
-;;
+;;
;; If `sh-make-vars-local' is non-nil, then these variables become
;; buffer local when the mode is established.
;; If this is nil, then the variables are global. At any time you
;; can make them local with the command `sh-make-vars-local'.
;; Conversely, to update with the global values you can use the
;; command `sh-reset-indent-vars-to-global-values'.
-;;
+;;
;; This may be awkward, but the intent is to cover all cases.
-;;
+;;
;; Awkward things, pitfalls
;; ------------------------
;; Indentation for a sh script is complicated for a number of reasons:
-;;
+;;
;; 1. You can't format by simply looking at symbols, you need to look
;; at keywords. [This is not the case for rc and es shells.]
;; 2. The character ")" is used both as a matched pair "(" ... ")" and
;; 4. A line may be continued using the "\".
;; 5. The character "#" (outside a string) normally starts a comment,
;; but it doesn't in the sequence "$#"!
-;;
+;;
;; To try and address points 2 3 and 5 I used a feature that cperl mode
;; uses, that of a text's syntax property. This, however, has 2
;; disadvantages:
;; buffer is read-only buffer we have to cheat and bypass the read-only
;; status. This is for cases where the buffer started read-only buffer
;; but the user issued `toggle-read-only'.
-;;
+;;
;; Bugs
;; ----
-;; - Here-documents are marked with text properties face and syntax
-;; table. This serves 2 purposes: stopping indentation while inside
-;; them, and moving over them when finding the previous line to
-;; indent to. However, if font-lock mode is active when there is
-;; any change inside the here-document font-lock clears that
-;; property. This causes several problems: lines after the here-doc
-;; will not be re-indented properly, words in the here-doc region
-;; may be fontified, and indentation may occur within the
-;; here-document.
-;; I'm not sure how to fix this, perhaps using the point-entered
-;; property. Anyway, if you use font lock and change a
-;; here-document, I recommend using M-x sh-rescan-buffer after the
-;; changes are made. Similarly, when using highlight-changes-mode,
-;; changes inside a here-document may confuse shell indenting, but again
-;; using `sh-rescan-buffer' should fix them.
-;;
;; - Indenting many lines is slow. It currently does each line
;; independently, rather than saving state information.
-;;
+;;
;; - `sh-learn-buffer-indent' is extremely slow.
-;;
+;;
;; Richard Sharman <rsharman@pobox.com> June 1999.
;;; Code:
;; page 4: statement syntax-commands for various shells
;; page 5: various other commands
+(eval-when-compile
+ (require 'skeleton)
+ (require 'comint))
(require 'executable)
csh C Shell
jcsh C Shell with Job Control
- tcsh Toronto C Shell
- itcsh ? Toronto C Shell
+ tcsh Turbo C Shell
+ itcsh ? Turbo C Shell
rc Plan 9 Shell
es Extensible Shell
sh Bourne Shell
(defcustom sh-alias-alist
- (nconc (if (eq system-type 'gnu/linux)
+ (append (if (eq system-type 'gnu/linux)
'((csh . tcsh)
(ksh . pdksh)))
;; for the time being
:group 'sh-script)
(defcustom sh-imenu-generic-expression
- (list
- (cons 'sh
- (concat
- "\\(^\\s-*function\\s-+[A-Za-z_][A-Za-z_0-9]*\\)"
- "\\|"
- "\\(^\\s-*[A-Za-z_][A-Za-z_0-9]*\\s-*()\\)")))
- "*Regular expression for recognizing shell function definitions.
-See `sh-feature'."
- :type '(repeat (cons (symbol :tag "Shell")
- regexp))
+ `((sh
+ . ((nil "^\\s-*\\(function\\s-+\\)?\\([A-Za-z_][A-Za-z_0-9]+\\)\\s-*()" 2))))
+ "*Alist of regular expressions for recognizing shell function definitions.
+See `sh-feature' and `imenu-generic-expression'."
+ :type '(alist :key-type (symbol :tag "Shell")
+ :value-type (alist :key-type (choice :tag "Title"
+ string
+ (const :tag "None" nil))
+ :value-type
+ (repeat :tag "Regexp, index..." sexp)))
:group 'sh-script
:version "20.4")
(defvar sh-shell (sh-canonicalize-shell (file-name-nondirectory sh-shell-file))
"The shell being programmed. This is set by \\[sh-set-shell].")
-;;; I turned off this feature because it doesn't permit typing commands
-;;; in the usual way without help.
-;;;(defvar sh-abbrevs
-;;; '((csh eval sh-abbrevs shell
-;;; "switch" 'sh-case
-;;; "getopts" 'sh-while-getopts)
-
-;;; (es eval sh-abbrevs shell
-;;; "function" 'sh-function)
-
-;;; (ksh88 eval sh-abbrevs sh
-;;; "select" 'sh-select)
-
-;;; (rc eval sh-abbrevs shell
-;;; "case" 'sh-case
-;;; "function" 'sh-function)
-
-;;; (sh eval sh-abbrevs shell
-;;; "case" 'sh-case
-;;; "function" 'sh-function
-;;; "until" 'sh-until
-;;; "getopts" 'sh-while-getopts)
-
-;;; ;; The next entry is only used for defining the others
-;;; (shell "for" sh-for
-;;; "loop" sh-indexed-loop
-;;; "if" sh-if
-;;; "tmpfile" sh-tmp-file
-;;; "while" sh-while)
-
-;;; (zsh eval sh-abbrevs ksh88
-;;; "repeat" 'sh-repeat))
-;;; "Abbrev-table used in Shell-Script mode. See `sh-feature'.
+;; I turned off this feature because it doesn't permit typing commands
+;; in the usual way without help.
+;;(defvar sh-abbrevs
+;; '((csh eval sh-abbrevs shell
+;; "switch" 'sh-case
+;; "getopts" 'sh-while-getopts)
+
+;; (es eval sh-abbrevs shell
+;; "function" 'sh-function)
+
+;; (ksh88 eval sh-abbrevs sh
+;; "select" 'sh-select)
+
+;; (rc eval sh-abbrevs shell
+;; "case" 'sh-case
+;; "function" 'sh-function)
+
+;; (sh eval sh-abbrevs shell
+;; "case" 'sh-case
+;; "function" 'sh-function
+;; "until" 'sh-until
+;; "getopts" 'sh-while-getopts)
+
+;; ;; The next entry is only used for defining the others
+;; (shell "for" sh-for
+;; "loop" sh-indexed-loop
+;; "if" sh-if
+;; "tmpfile" sh-tmp-file
+;; "while" sh-while)
+
+;; (zsh eval sh-abbrevs ksh88
+;; "repeat" 'sh-repeat))
+;; "Abbrev-table used in Shell-Script mode. See `sh-feature'.
;;;Due to the internal workings of abbrev tables, the shell name symbol is
;;;actually defined as the table for the like of \\[edit-abbrevs].")
(defvar sh-mode-syntax-table
'((sh eval sh-mode-syntax-table ()
?\# "<"
- ?\^l ">#"
?\n ">#"
?\" "\"\""
?\' "\"'"
?: "_"
?. "_"
?^ "_"
- ?~ "_")
+ ?~ "_"
+ ?< "."
+ ?> ".")
(csh eval identity sh)
(rc eval identity sh))
- "Syntax-table used in Shell-Script mode. See `sh-feature'.")
-
+ "Syntax-table used in Shell-Script mode. See `sh-feature'.")
(defvar sh-mode-map
(let ((map (make-sparse-keymap))
(define-key map "'" 'skeleton-pair-insert-maybe)
(define-key map "`" 'skeleton-pair-insert-maybe)
(define-key map "\"" 'skeleton-pair-insert-maybe)
- (define-key map ")" 'sh-electric-rparen)
- (define-key map "<" 'sh-electric-less)
- (define-key map "#" 'sh-electric-hash)
-
- (substitute-key-definition 'complete-tag 'comint-dynamic-complete
- map (current-global-map))
- (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
- map (current-global-map))
- (substitute-key-definition 'delete-backward-char
- 'backward-delete-char-untabify
- map (current-global-map))
+
+ (define-key map [remap complete-tag] 'comint-dynamic-complete)
+ (define-key map [remap newline-and-indent] 'sh-newline-and-indent)
+ (define-key map [remap delete-backward-char]
+ 'backward-delete-char-untabify)
(define-key map "\C-c:" 'sh-set-shell)
- (substitute-key-definition 'beginning-of-defun
- 'sh-beginning-of-compound-command
- map (current-global-map))
- (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
- map (current-global-map))
- (substitute-key-definition 'forward-sentence 'sh-end-of-command
- map (current-global-map))
+ (define-key map [remap beginning-of-defun]
+ 'sh-beginning-of-compound-command)
+ (define-key map [remap backward-sentence] 'sh-beginning-of-command)
+ (define-key map [remap forward-sentence] 'sh-end-of-command)
(define-key map [menu-bar insert] (cons "Insert" menu-map))
(define-key menu-map [sh-while] '("While Loop" . sh-while))
(define-key menu-map [sh-until] '("Until Loop" . sh-until))
(define-key menu-map [sh-tmp-file] '("Temporary File" . sh-tmp-file))
(define-key menu-map [sh-select] '("Select Statement" . sh-select))
(define-key menu-map [sh-repeat] '("Repeat Loop" . sh-repeat))
- (define-key menu-map [sh-while-getopts]
- '("Options Loop" . sh-while-getopts))
- (define-key menu-map [sh-indexed-loop]
- '("Indexed Loop" . sh-indexed-loop))
+ (define-key menu-map [sh-getopts] '("Options Loop" . sh-while-getopts))
+ (define-key menu-map [sh-indexed-loop] '("Indexed Loop" . sh-indexed-loop))
(define-key menu-map [sh-if] '("If Statement" . sh-if))
(define-key menu-map [sh-for] '("For Loop" . sh-for))
(define-key menu-map [sh-case] '("Case Statement" . sh-case))
"bg" "fg" "jobs" "kill" "stop" "suspend")
(jcsh eval sh-append csh
- "bg" "fg" "jobs" "kill" "notify" "stop" "suspend")
+ "bg" "fg" "jobs" "kill" "notify" "stop" "suspend")
(ksh88 eval sh-append bourne
"alias" "bg" "false" "fc" "fg" "jobs" "kill" "let" "print" "time"
"pid" "prompt" "signals")
(jcsh eval sh-append csh
- "notify")
+ "notify")
(ksh88 eval sh-append sh
"ENV" "ERRNO" "FCEDIT" "FPATH" "HISTFILE" "HISTSIZE" "LINENO"
"List of all shell variables available for completing read.
See `sh-feature'.")
+\f
+;; Font-Lock support
+
+(defface sh-heredoc-face
+ '((((class color)
+ (background dark))
+ (:foreground "yellow" :weight bold))
+ (((class color)
+ (background light))
+ (:foreground "tan" ))
+ (t
+ (:weight bold)))
+ "Face to show a here-document"
+ :group 'sh-indentation)
+(defvar sh-heredoc-face 'sh-heredoc-face)
(defvar sh-font-lock-keywords
(defvar sh-font-lock-keywords-2 ()
"Gaudy level highlighting for Shell Script modes.")
+;; 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-here-doc-open-re "<<-?\\s-*\\\\?\\(\\(?:['\"][^'\"]+['\"]\\|\\sw\\|\\s_\\)+\\).*\\(\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 <<EOF back.
+ (sre (concat "<<\\(-?\\)\\s-*['\"\\]?"
+ ;; Use \s| to cheaply check it's an open-heredoc.
+ eof-re "['\"]?\\([ \t|;&)<>].*\\)?\\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 <<EOF1 <<EOF2'.
+ (save-excursion
+ (goto-char start)
+ (setq start (line-beginning-position 2))
+ (while
+ (progn
+ (re-search-forward "<<") ; Skip ourselves.
+ (and (re-search-forward sh-here-doc-open-re start 'move)
+ (goto-char (match-beginning 0))
+ (sh-in-comment-or-string (point)))))
+ ;; No <<EOF2 found after our <<.
+ (= (point) start)))
+ 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.
+ (setq sh-here-doc-markers (delete eof sh-here-doc-markers))
+ (setq sh-here-doc-re
+ (concat sh-here-doc-open-re "\\|^\\([ \t]*\\)"
+ (regexp-opt sh-here-doc-markers t) "\\(\n\\)"))
+ nil))))
+
+(defun sh-font-lock-open-heredoc (start string)
+ "Determine the syntax of the \\n after a <<EOF.
+START is the position of <<.
+STRING is the actual word used as delimiter (f.ex. \"EOF\").
+INDENTED is non-nil if the here document's content (and the EOF mark) can
+be indented (i.e. a <<- was used rather than just <<)."
+ (unless (or (memq (char-before start) '(?< ?>))
+ (sh-in-comment-or-string start))
+ ;; We're looking at <<STRING, so we add "^STRING$" to the syntactic
+ ;; font-lock keywords to detect the end of this here document.
+ (let ((str (replace-regexp-in-string "['\"]" "" string)))
+ (unless (member str sh-here-doc-markers)
+ (push str sh-here-doc-markers)
+ (setq sh-here-doc-re
+ (concat sh-here-doc-open-re "\\|^\\([ \t]*\\)"
+ (regexp-opt sh-here-doc-markers t) "\\(\n\\)"))))
+ 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))
+
+(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 "?*/"))
+ (when (memq (char-before) '(?\" ?\'))
+ (condition-case nil (progn (backward-sexp 1) t)
+ (error nil)))))
+ (forward-comment (- (point-max)))
+ (when (eq (char-before) ?|)
+ (backward-char 1) t)))
+ (when (save-excursion (backward-char 2) (looking-at ";;\\|in"))
+ sh-st-punc)))
+
(defconst sh-font-lock-syntactic-keywords
- ;; Mark a `#' character as having punctuation syntax in a variable reference.
- ;; Really we should do this properly. From Chet Ramey and Brian Fox:
- ;; "A `#' begins a comment when it is unquoted and at the beginning of a
- ;; word. In the shell, words are separated by metacharacters."
- ;; To do this in a regexp would be slow as it would be anchored to the right.
- ;; But I can't be bothered to write a function to do it properly and
- ;; efficiently. So we only do it properly for `#' in variable references and
- ;; do it efficiently by anchoring the regexp to the left.
- '(("\\${?[^}#\n\t ]*\\(##?\\)" 1 (1 . nil))))
+ ;; 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)
+ ;; 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)))))
+
+(defun sh-font-lock-syntactic-face-function (state)
+ (if (nth 3 state)
+ (if (char-valid-p (nth 3 state))
+ font-lock-string-face
+ sh-heredoc-face)
+ font-lock-comment-face))
(defgroup sh-indentation nil
"Variables controlling indentation in shell scripts.
(defcustom sh-set-shell-hook nil
"*Hook run by `sh-set-shell'."
- :type 'hook
+ :type 'hook
:group 'sh-script)
(defcustom sh-mode-hook nil
"*Hook run by `sh-mode'."
- :type 'hook
+ :type 'hook
:group 'sh-script)
(defcustom sh-learn-basic-offset nil
(const :tag "Leave as is." nil)
(const :tag "Indent as a normal line." t)
(integer :menu-tag "Indent to this col (0 means first col)."
- :tag "Indent to column number.") )
+ :tag "Indent to column number.") )
:group 'sh-indentation)
:group 'sh-indentation)
(defconst sh-number-or-symbol-list
- (append (list '(integer :menu-tag "A number (positive=>indent right)"
- :tag "A number")
- '(const :tag "--")) ; separator
+ (append '((integer :menu-tag "A number (positive=>indent right)"
+ :tag "A number")
+ (const :tag "--")) ; separator
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 an 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."
+ "*How much to indent a done relative to its matching stmt. Usually 0."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
:group 'sh-indentation)
(defcustom sh-indent-for-then '+
- "*How much to indent an then relative to an if."
+ "*How much to indent a then relative to an if."
:type `(choice ,@ sh-number-or-symbol-list )
:group 'sh-indentation)
:group 'sh-indentation)
(defcustom sh-indent-after-do '*
-"*How much to indent a line after a do statement.
+ "*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."
:type `(choice ,@ sh-number-or-symbol-list)
:group 'sh-indentation)
-(defface sh-heredoc-face
- '((((class color)
- (background dark))
- (:foreground "yellow" :bold t))
- (((class color)
- (background light))
- (:foreground "tan" ))
- (t
- (:bold t)))
- "Face to show a here-document"
- :group 'sh-indentation)
-
-(defface sh-st-face
- '((((class color)
- (background dark))
- (:foreground "yellow" :bold t))
- (((class color)
- (background light))
- (:foreground "tan" ))
- (t
- (:bold t)))
- "Face to show characters with special syntax properties."
- :group 'sh-indentation)
-
-
;; Internal use - not designed to be changed by the user:
-;; 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-here-doc-syntax '(15)) ;; generic string
-(defconst sh-st-punc '(1))
-(defconst sh-special-syntax sh-st-punc)
-
(defun sh-mkword-regexpr (word)
"Make a regexp which matches WORD as a word.
This specifically excludes an occurrence of WORD followed by
punctuation characters like '-'."
(concat word "\\([^-a-z0-9_]\\|$\\)"))
-(defun sh-mkword-regexp (word)
- "Make a regexp which matches WORD as a word.
-This specifically excludes an occurrence of WORD followed by
-or preceded by punctuation characters like '-'."
- (concat "\\(^\\|[^-a-z0-9_]\\)" word "\\([^-a-z0-9_]\\|$\\)"))
-
(defconst sh-re-done (sh-mkword-regexpr "done"))
(defvar sh-indent-supported-here nil
"Non-nil if we support indentation for the current buffer's shell type.")
-(defconst sh-electric-rparen-needed
- '((sh . t))
- "Non-nil if the shell type needs an electric handling of case alternatives.")
-
-(defvar sh-electric-rparen-needed-here nil
- "Non-nil if the buffer needs an electric handling of case alternatives.")
-
(defconst sh-var-list
'(
sh-basic-offset sh-first-lines-indent sh-indent-after-case
\\[sh-execute-region] Have optional header and region be executed in a subshell.
\\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document.
-{, (, [, ', \", `
+\{, (, [, ', \", `
Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
If you generally program a shell different from your login shell you can
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 'indent-line-function)
- (make-local-variable 'indent-region-function)
(make-local-variable 'skeleton-end-hook)
(make-local-variable 'paragraph-start)
(make-local-variable 'paragraph-separate)
(make-local-variable 'sh-shell-variables)
(make-local-variable 'sh-shell-variables-initialized)
(make-local-variable 'imenu-generic-expression)
- (make-local-variable 'sh-electric-rparen-needed-here)
(make-local-variable 'sh-indent-supported-here)
- (make-local-variable 'font-lock-unfontify-region-function)
- (setq major-mode 'sh-mode
- mode-name "Shell-script"
- ;; not very clever, but enables wrapping skeletons around regions
- indent-region-function (lambda (b e)
- (save-excursion
- (goto-char b)
- (skip-syntax-backward "-")
- (setq b (point))
- (goto-char e)
- (skip-syntax-backward "-")
- (indent-rigidly b (point) sh-indentation)))
- skeleton-end-hook (lambda ()
+ (setq skeleton-end-hook (lambda ()
(or (eolp) (newline) (indent-relative)))
paragraph-start (concat page-delimiter "\\|$")
paragraph-separate paragraph-start
;; we can't look if previous line ended with `\'
comint-prompt-regexp "^[ \t]*"
font-lock-defaults
- '((sh-font-lock-keywords
+ `((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-unfontify-region-function
- 'sh-font-lock-unfontify-region-function
+ (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 'sh-quoted-p
skeleton-further-elements '((< '(- (min sh-indentation
(current-column)))))
skeleton-filter 'sh-feature
skeleton-newline-indent-rigidly t
- sh-electric-rparen-needed-here nil
sh-indent-supported-here nil)
- (make-local-variable 'parse-sexp-ignore-comments)
- (setq parse-sexp-ignore-comments 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.
(let ((interpreter
((and buffer-file-name
(string-match "\\.m?spec$" buffer-file-name))
"rpm")))))
- (if interpreter
- (sh-set-shell interpreter nil nil)
- (sh-set-shell sh-shell-file nil nil))
- (run-hooks 'sh-mode-hook)))
+ (sh-set-shell (or interpreter sh-shell-file) nil nil))
+ (run-hooks 'sh-mode-hook))
+
;;;###autoload
(defalias 'shell-script-mode 'sh-mode)
(defun sh-font-lock-keywords-1 (&optional builtins)
"Function to get better fontification including keywords."
- (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\(\\("
- (mapconcat 'identity
- (sh-feature sh-leading-keywords)
- "\\|")
- "\\)[ \t]+\\)?\\("
- (mapconcat 'identity
- (append (sh-feature sh-leading-keywords)
- (sh-feature sh-other-keywords))
- "\\|")
- "\\)")))
+ (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\("
+ (regexp-opt (sh-feature sh-leading-keywords) t)
+ "[ \t]+\\)?"
+ (regexp-opt (append (sh-feature sh-leading-keywords)
+ (sh-feature sh-other-keywords))
+ t))))
(sh-font-lock-keywords
`(,@(if builtins
- `((,(concat keywords "[ \t]+\\)?\\("
- (mapconcat 'identity (sh-feature sh-builtins) "\\|")
- "\\)\\>")
+ `((,(concat keywords "[ \t]+\\)?"
+ (regexp-opt (sh-feature sh-builtins) t)
+ "\\>")
(2 font-lock-keyword-face nil t)
(6 font-lock-builtin-face))
,@(sh-feature sh-font-lock-keywords-2)))
;; for both.
;;
(defconst sh-kw
- '(
- (sh
- ( "if"
- nil
- sh-handle-prev-if )
- ( "elif"
- sh-handle-this-else
- sh-handle-prev-else )
- ( "else"
- sh-handle-this-else
- sh-handle-prev-else )
- ( "fi"
- sh-handle-this-fi
- sh-handle-prev-fi )
- ( "then"
- sh-handle-this-then
- sh-handle-prev-then )
- ( "("
- nil
- sh-handle-prev-open )
- ( "{"
- nil
- sh-handle-prev-open )
- ( "["
- nil
- sh-handle-prev-open )
- ( "}"
- sh-handle-this-close
- nil )
- ( ")"
- sh-handle-this-close
- nil )
- ( "]"
- sh-handle-this-close
- nil )
- ( "case"
- nil
- sh-handle-prev-case )
- ( "esac"
- sh-handle-this-esac
- sh-handle-prev-esac )
- ( case-label
- nil ;; ???
- sh-handle-after-case-label )
- ( ";;"
- nil ;; ???
- sh-handle-prev-case-alt-end ;; ??
- )
- ( "done"
- sh-handle-this-done
- sh-handle-prev-done )
- ( "do"
- sh-handle-this-do
- sh-handle-prev-do )
- ) ;; end of sh
+ '((sh
+ ("if" nil sh-handle-prev-if)
+ ("elif" sh-handle-this-else sh-handle-prev-else)
+ ("else" sh-handle-this-else sh-handle-prev-else)
+ ("fi" sh-handle-this-fi sh-handle-prev-fi)
+ ("then" sh-handle-this-then sh-handle-prev-then)
+ ("(" nil sh-handle-prev-open)
+ ("{" nil sh-handle-prev-open)
+ ("[" nil sh-handle-prev-open)
+ ("}" sh-handle-this-close nil)
+ (")" sh-handle-this-close nil)
+ ("]" sh-handle-this-close nil)
+ ("case" nil sh-handle-prev-case)
+ ("esac" sh-handle-this-esac sh-handle-prev-esac)
+ (case-label nil sh-handle-after-case-label) ;; ???
+ (";;" nil sh-handle-prev-case-alt-end) ;; ???
+ ("done" sh-handle-this-done sh-handle-prev-done)
+ ("do" sh-handle-this-do sh-handle-prev-do))
;; Note: we don't need specific stuff for bash and zsh shells;
;; the regexp `sh-regexp-for-done' handles the extra keywords
;; these shells use.
(rc
- ( "{"
- nil
- sh-handle-prev-open )
- ( "}"
- sh-handle-this-close
- nil )
- ( "case"
- sh-handle-this-rc-case
- sh-handle-prev-rc-case )
- ) ;; end of rc
- ))
+ ("{" nil sh-handle-prev-open)
+ ("}" sh-handle-this-close nil)
+ ("case" sh-handle-this-rc-case sh-handle-prev-rc-case))))
(defun sh-set-shell (shell &optional no-query-flag insert-flag)
"Set this buffer's shell to SHELL (a string).
-Makes this script executable via `executable-set-magic', and sets up the
-proper starting #!-line, if INSERT-FLAG is non-nil.
+When used interactively, insert the proper starting #!-line,
+and make the visited file executable via `executable-set-magic',
+perhaps querying depending on the value of `executable-query'.
+
+When this function is called noninteractively, INSERT-FLAG (the third
+argument) controls whether to insert a #!-line and think about making
+the visited file executable, and NO-QUERY-FLAG (the second argument)
+controls whether to query about making the visited file executable.
+
Calls the value of `sh-set-shell-hook' if set."
- (interactive (list (completing-read "Name or path of shell: "
- interpreter-mode-alist
- (lambda (x) (eq (cdr x) 'sh-mode)))
+ (interactive (list (completing-read (format "Shell \(default %s\): "
+ sh-shell-file)
+ interpreter-mode-alist
+ (lambda (x) (eq (cdr x) 'sh-mode))
+ nil nil nil sh-shell-file)
(eq executable-query 'function)
t))
(if (string-match "\\.exe\\'" shell)
no-query-flag insert-flag)))
(setq require-final-newline (sh-feature sh-require-final-newline)
;;; local-abbrev-table (sh-feature sh-abbrevs)
-;; Packages should not need to set these variables directly. sm.
-; font-lock-keywords nil ; force resetting
-; font-lock-syntax-table nil
comment-start-skip "#+[\t ]*"
mode-line-process (format "[%s]" sh-shell)
sh-shell-variables nil
imenu-case-fold-search nil)
(set-syntax-table (or (sh-feature sh-mode-syntax-table)
(standard-syntax-table)))
- (let ((vars (sh-feature sh-variables)))
- (while vars
- (sh-remember-variable (car vars))
- (setq vars (cdr vars))))
-;; Packages should not need to toggle Font Lock mode. sm.
-; (and (boundp 'font-lock-mode)
-; font-lock-mode
-; (font-lock-mode (font-lock-mode 0)))
+ (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)
- (make-local-variable 'sh-kw-alist)
- (make-local-variable 'sh-regexp-for-done)
- (make-local-variable 'parse-sexp-lookup-properties)
- (setq sh-electric-rparen-needed-here
- (sh-feature sh-electric-rparen-needed))
- (setq parse-sexp-lookup-properties t)
- (sh-scan-buffer)
- (setq sh-kw-alist (sh-feature sh-kw))
+ (set (make-local-variable 'parse-sexp-lookup-properties) t)
+ (set (make-local-variable 'sh-kw-alist) (sh-feature sh-kw))
(let ((regexp (sh-feature sh-kws-for-done)))
(if regexp
- (setq sh-regexp-for-done
- (sh-mkword-regexpr (regexp-opt regexp t)))))
+ (set (make-local-variable 'sh-regexp-for-done)
+ (sh-mkword-regexpr (regexp-opt regexp t)))))
(message "setting up indent stuff")
;; sh-mode has already made indent-line-function local
;; but do it in case this is called before that.
- (make-local-variable 'indent-line-function)
(setq indent-line-function 'sh-indent-line)
- ;; This is very inefficient, but this at least makes indent-region work:
- (make-local-variable 'indent-region-function)
- (setq indent-region-function nil)
(if sh-make-vars-local
(sh-make-vars-local))
(message "Indentation setup for shell type %s" sh-shell))
-;;; I commented this out because nobody calls it -- rms.
-;;;(defun sh-abbrevs (ancestor &rest list)
-;;; "Iff it isn't, define the current shell as abbrev table and fill that.
-;;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev
-;;;table or a list of (NAME1 EXPANSION1 ...). In addition it will define abbrevs
-;;;according to the remaining arguments NAMEi EXPANSIONi ...
-;;;EXPANSION may be either a string or a skeleton command."
-;;; (or (if (boundp sh-shell)
-;;; (symbol-value sh-shell))
-;;; (progn
-;;; (if (listp ancestor)
-;;; (nconc list ancestor))
-;;; (define-abbrev-table sh-shell ())
-;;; (if (vectorp ancestor)
-;;; (mapatoms (lambda (atom)
-;;; (or (eq atom 0)
-;;; (define-abbrev (symbol-value sh-shell)
-;;; (symbol-name atom)
-;;; (symbol-value atom)
-;;; (symbol-function atom))))
-;;; ancestor))
-;;; (while list
-;;; (define-abbrev (symbol-value sh-shell)
-;;; (car list)
-;;; (if (stringp (car (cdr list)))
-;;; (car (cdr list))
-;;; "")
-;;; (if (symbolp (car (cdr list)))
-;;; (car (cdr list))))
-;;; (setq list (cdr (cdr list)))))
-;;; (symbol-value sh-shell)))
+;; I commented this out because nobody calls it -- rms.
+;;(defun sh-abbrevs (ancestor &rest list)
+;; "Iff it isn't, define the current shell as abbrev table and fill that.
+;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev
+;;table or a list of (NAME1 EXPANSION1 ...). In addition it will define abbrevs
+;;according to the remaining arguments NAMEi EXPANSIONi ...
+;;EXPANSION may be either a string or a skeleton command."
+;; (or (if (boundp sh-shell)
+;; (symbol-value sh-shell))
+;; (progn
+;; (if (listp ancestor)
+;; (nconc list ancestor))
+;; (define-abbrev-table sh-shell ())
+;; (if (vectorp ancestor)
+;; (mapatoms (lambda (atom)
+;; (or (eq atom 0)
+;; (define-abbrev (symbol-value sh-shell)
+;; (symbol-name atom)
+;; (symbol-value atom)
+;; (symbol-function atom))))
+;; ancestor))
+;; (while list
+;; (define-abbrev (symbol-value sh-shell)
+;; (car list)
+;; (if (stringp (car (cdr list)))
+;; (car (cdr list))
+;; "")
+;; (if (symbolp (car (cdr list)))
+;; (car (cdr list))))
+;; (setq list (cdr (cdr list)))))
+;; (symbol-value sh-shell)))
(defun sh-mode-syntax-table (table &rest list)
"Copy TABLE and set syntax for successive CHARs according to strings S."
(setq table (copy-syntax-table table))
(while list
- (modify-syntax-entry (car list) (car (cdr list)) table)
- (setq list (cdr (cdr list))))
+ (modify-syntax-entry (pop list) (pop list) table))
table)
-
(defun sh-append (ancestor &rest list)
"Return list composed of first argument (a list) physically appended to rest."
(nconc list ancestor))
(or (< (length var) sh-remember-variable-min)
(getenv var)
(assoc var sh-shell-variables)
- (setq sh-shell-variables (cons (cons var var) sh-shell-variables)))
+ (push (cons var var) sh-shell-variables))
var)
(eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2)))
\f
;; Indentation stuff.
-(defun sh-must-be-shell-mode ()
- "Signal an error if not in Shell-script mode."
- (unless (eq major-mode 'sh-mode)
- (error "This buffer is not in Shell-script mode")))
-
(defun sh-must-support-indent ()
"*Signal an error if the shell type for this buffer is not supported.
Also, the buffer must be in Shell-script mode."
- (sh-must-be-shell-mode)
(unless sh-indent-supported-here
- (error "This buffer's shell type is not supported for this command")))
+ (error "This buffer's shell does not support indentation through Emacs")))
(defun sh-make-vars-local ()
"Make the indentation variables local to this buffer.
To revert all these variables to the global values, use
command `sh-reset-indent-vars-to-global-values'."
(interactive)
- (sh-must-be-shell-mode)
(mapcar 'make-local-variable sh-var-list)
(message "Indentation variable are now local."))
"Reset local indentation variables to the global values.
Then, if variable `sh-make-vars-local' is non-nil, make them local."
(interactive)
- (sh-must-be-shell-mode)
(mapcar 'kill-local-variable sh-var-list)
(if sh-make-vars-local
(mapcar 'make-local-variable sh-var-list)))
"Construct a string for `sh-read-variable' when changing variable VAR ."
(let ((msg (documentation-property var 'variable-documentation))
(msg2 ""))
- (unless (or
- (eq var 'sh-first-lines-indent)
- (eq var 'sh-indent-comment))
+ (unless (memq var '(sh-first-lines-indent sh-indent-comment))
(setq msg2
(format "\n
You can enter a number (positive to increase indentation,
\t%s."
sh-basic-offset
- (mapconcat (lambda (x)
- (nth (1- (length x)) x) )
- sh-symbol-list "\n\t")
- )))
-
+ (mapconcat (lambda (x)
+ (nth (1- (length x)) x))
+ sh-symbol-list "\n\t"))))
(concat
;; The following shows the global not the local value!
;; (format "Current value of %s is %s\n\n" var (symbol-value var))
(quote ,var)))
val)
(setq val (read-from-minibuffer
- (format "New value for %s (press %s for help): "
- var (single-key-description help-char))
- (format "%s" (symbol-value var))
- nil t))
+ (format "New value for %s (press %s for help): "
+ var (single-key-description help-char))
+ (format "%s" (symbol-value var))
+ nil t))
val))
(defun sh-in-comment-or-string (start)
"Return non-nil if START is in a comment or string."
(save-excursion
- (let (state)
- (beginning-of-line)
- (setq state (parse-partial-sexp (point) start nil nil nil t))
- (or (nth 3 state)(nth 4 state)))))
+ (let ((state (syntax-ppss start)))
+ (or (nth 3 state) (nth 4 state)))))
(defun sh-goto-matching-if ()
"Go to the matching if for a fi.
(defun sh-handle-this-close ()
(forward-char 1) ;; move over ")"
- (let ((p (sh-safe-backward-sexp)))
- (if p
- (list "aligned to opening paren")
- nil
- )))
+ (if (sh-safe-forward-sexp -1)
+ (list "aligned to opening paren")))
(defun sh-goto-matching-case ()
(let ((found (sh-find-prev-matching "\\bcase\\b" "\\besac\\b" 1)))
- (if found
- (goto-char found))))
+ (if found (goto-char found))))
(defun sh-handle-prev-case ()
;; This is typically called when point is on same line as a case
;; we shouldn't -- and can't find prev-case
- (if (looking-at ".*\\bcase\\b")
+ (if (looking-at ".*\\<case\\>")
(list '(+ sh-indent-for-case-label))
- (error "We don't seem to be on a line with a case") ;; debug
- ))
+ (error "We don't seem to be on a line with a case"))) ;; debug
(defun sh-handle-this-esac ()
- (let ((p (sh-goto-matching-case)))
- (if p
- (list "aligned to matching case")
- nil
- )))
-
+ (if (sh-goto-matching-case)
+ (list "aligned to matching case")))
(defun sh-handle-prev-esac ()
- (let ((p (sh-goto-matching-case)))
- (if p
- (list "matching case")
- nil
- )))
+ (if (sh-goto-matching-case)
+ (list "matching case")))
(defun sh-handle-after-case-label ()
- (let ((p (sh-goto-matching-case)))
- (if p
- (list '( + sh-indent-for-case-alt ))
- nil
- )))
+ (if (sh-goto-matching-case)
+ (list '(+ sh-indent-for-case-alt))))
(defun sh-handle-prev-case-alt-end ()
- (let ((p (sh-goto-matching-case)))
- (if p
- (list '( + sh-indent-for-case-label ))
- nil
- )))
+ (if (sh-goto-matching-case)
+ (list '(+ sh-indent-for-case-label))))
-(defun sh-safe-backward-sexp ()
- "Try and do a `backward-sexp', but do not error.
-Return new point if successful, nil if an error occurred."
- (condition-case nil
- (progn
- (backward-sexp 1)
- (point) ;; return point if successful
- )
- (error
- (sh-debug "oops!(0) %d" (point))
- nil ;; return nil if fail
- )))
-
-(defun sh-safe-forward-sexp ()
+(defun sh-safe-forward-sexp (&optional arg)
"Try and do a `forward-sexp', but do not error.
Return new point if successful, nil if an error occurred."
(condition-case nil
(progn
- (forward-sexp 1)
- (point) ;; return point if successful
- )
+ (forward-sexp (or arg 1))
+ (point)) ;; return point if successful
(error
(sh-debug "oops!(1) %d" (point))
- nil ;; return nil if fail
- )))
+ nil))) ;; return nil if fail
(defun sh-goto-match-for-done ()
(let ((found (sh-find-prev-matching sh-regexp-for-done sh-re-done 1)))
(defun sh-handle-this-done ()
(if (sh-goto-match-for-done)
- (list "aligned to do stmt" '(+ sh-indent-for-done))
- nil
- ))
+ (list "aligned to do stmt" '(+ sh-indent-for-done))))
(defun sh-handle-prev-done ()
(if (sh-goto-match-for-done)
- (list "previous done")
- nil
- ))
+ (list "previous done")))
(defun sh-handle-this-do ()
- (let ( (p (sh-goto-match-for-done)) )
- (if p
- (list '(+ sh-indent-for-do))
- nil
- )))
+ (if (sh-goto-match-for-done)
+ (list '(+ sh-indent-for-do))))
(defun sh-handle-prev-do ()
- (let ( (p) )
- (cond
- ((save-restriction
- (narrow-to-region
- (point)
- (save-excursion
- (beginning-of-line)
- (point)))
- (sh-goto-match-for-done))
- (sh-debug "match for done found on THIS line")
- (list '(+ sh-indent-after-loop-construct)))
- ((sh-goto-match-for-done)
- (sh-debug "match for done found on PREV line")
- (list '(+ sh-indent-after-do)))
- (t
- (message "match for done NOT found")
- nil))))
+ (cond
+ ((save-restriction
+ (narrow-to-region
+ (point)
+ (save-excursion
+ (beginning-of-line)
+ (point)))
+ (sh-goto-match-for-done))
+ (sh-debug "match for done found on THIS line")
+ (list '(+ sh-indent-after-loop-construct)))
+ ((sh-goto-match-for-done)
+ (sh-debug "match for done found on PREV line")
+ (list '(+ sh-indent-after-do)))
+ (t
+ (message "match for done NOT found")
+ nil)))
;; for rc:
(defun sh-find-prev-switch ()
(defun sh-handle-this-rc-case ()
(if (sh-find-prev-switch)
(list '(+ sh-indent-after-switch))
- ;; (list '(+ sh-indent-for-case-label))
+ ;; (list '(+ sh-indent-for-case-label))
nil))
(defun sh-handle-prev-rc-case ()
prev-line-end x)
(beginning-of-line)
;; Note: setting result to t means we are done and will return nil.
- ;;( This function never returns just t.)
+ ;;(This function never returns just t.)
(cond
- ((equal (get-text-property (point) 'syntax-table) sh-here-doc-syntax)
+ ((or (and (boundp 'font-lock-string-face)
+ (eq (get-text-property (point) 'face) font-lock-string-face))
+ (eq (get-text-property (point) 'face) sh-heredoc-face))
(setq result t)
(setq have-result t))
((looking-at "\\s-*#") ; was (equal this-kw "#")
(if (bobp)
- (setq result t);; return nil if 1st line!
+ (setq result t) ;; return nil if 1st line!
(setq result (list '(= sh-indent-comment)))
;; we still need to get previous line in case
;; sh-indent-comment is t (indent as normal)
(setq align-point (sh-prev-line nil))
(setq have-result nil)
))
- );; cond
-
+ ) ;; cond
+
(unless have-result
;; Continuation lines are handled specially
(if (sh-this-is-a-continuation)
;; We start off at beginning of this line.
;; Scan previous statements while this is <=
;; start of previous line.
- (setq start (point));; for debug only
+ (setq start (point)) ;; for debug only
(goto-char prev-line-end)
(setq x t)
(while (and x (setq x (sh-prev-thing)))
(cond
((and (equal x ")")
(equal (get-text-property (1- (point)) 'syntax-table)
- sh-special-syntax))
+ sh-st-punc))
(sh-debug "Case label) here")
(setq x 'case-label)
(if (setq val (sh-check-rule 2 x))
(skip-chars-forward "[a-z0-9]*?")
)
((string-match "[])}]" x)
- (setq x (sh-safe-backward-sexp))
+ (setq x (sh-safe-forward-sexp -1))
(if x
(progn
(setq align-point (point))
((string-match "[\"'`]" x)
(sh-debug "Skipping back for %s" x)
;; this was oops-2
- (setq x (sh-safe-backward-sexp)))
+ (setq x (sh-safe-forward-sexp -1)))
((stringp x)
(sh-debug "Checking string %s at %s" x (point))
(if (setq val (sh-check-rule 2 x))
(t
(error "Don't know what to do with %s" x))
)
- );; while
+ ) ;; while
(sh-debug "result is %s" result)
)
(sh-debug "No prev line!")
(sh-debug "result: %s align-point: %s" result align-point)
)
-
+
(if align-point
;; was: (setq result (append result (list (list t align-point))))
(setq result (append (list (list t align-point)) result))
)
(sh-debug "result is now: %s" result)
-
+
(or result
(if prev-line-end
(setq result (list (list t prev-line-end)))
(setq result (list (list '= 'sh-first-lines-indent)))
))
-
+
(if (eq result t)
(setq result nil))
(sh-debug "result is: %s" result)
result
- );; let
+ ) ;; let
))
(error "sh-get-indent-var-for-line invalid elt: %s" elt))
;; so it is a list
((eq t (car elt))
- );; nothing
+ ) ;; nothing
((symbolp (setq sym (nth 1 elt)))
;; A bit of a kludge - when we see the sh-indent-comment
;; ignore other variables. Otherwise it is tricky to
;; because we may want to a align to the beginning of it.
;;
;; What we do:
-;; - go back a line, if empty repeat
-;; - (we are now at a previous non empty line)
-;; - save this
+;; - go back to previous non-empty line
;; - if this is in a here-document, go to the beginning of it
-;; and save that
-;; - go back one more physical line and see if it is a continuation line
-;; - if yes, save it and repeat
-;; - if no, go back to where we last saved.
+;; - while previous line is continued, go back one line
(defun sh-prev-line (&optional end)
"Back to end of previous non-comment non-empty line.
Go to beginning of logical line unless END is non-nil, in which case
we go to the end of the previous line and do not check for continuations."
- (sh-must-be-shell-mode)
- (let ((going t)
- (last-contin-line nil)
- (result nil)
- bol eol state)
- (save-excursion
+ (save-excursion
+ (beginning-of-line)
+ (forward-comment (- (point-max)))
+ (unless end (beginning-of-line))
+ (when (and (not (bobp))
+ (equal (get-text-property (1- (point)) 'face)
+ sh-heredoc-face))
+ (let ((p1 (previous-single-property-change (1- (point)) 'face)))
+ (when p1
+ (goto-char p1)
+ (if end
+ (end-of-line)
+ (beginning-of-line)))))
+ (unless end
+ ;; we must check previous lines to see if they are continuation lines
+ ;; if so, we must return position of first of them
+ (while (and (sh-this-is-a-continuation)
+ (>= 0 (forward-line -1))))
(beginning-of-line)
- (while (and going
- (not (bobp))
- (>= 0 (forward-line -1))
- )
- (setq bol (point))
- (end-of-line)
- (setq eol (point))
- (save-restriction
- (setq state (parse-partial-sexp bol eol nil nil nil t))
- (if (nth 4 state)
- (setq eol (nth 8 state)))
- (narrow-to-region bol eol)
- (goto-char bol)
- (cond
- ((looking-at "\\s-*$"))
- (t
- (if end
- (setq result eol)
- (setq result bol))
- (setq going nil))
- )))
- (if (and result
- (equal (get-text-property (1- result) 'syntax-table)
- sh-here-doc-syntax))
- (let ((p1 (previous-single-property-change
- (1- result) 'syntax-table)))
- (if p1
- (progn
- (goto-char p1)
- (forward-line -1)
- (if end
- (end-of-line))
- (setq result (point)))
- )))
- (unless end
- ;; we must check previous lines to see if they are continuation lines
- ;; if so, we must return position of first of them
- (while (and (sh-this-is-a-continuation)
- (>= 0 (forward-line -1)))
- (setq result (point)))
- (if result
- (progn
- (goto-char result)
- (beginning-of-line)
- (skip-chars-forward " \t")
- (setq result (point))
- )))
- ) ;; save-excursion
- result
- ))
+ (skip-chars-forward " \t"))
+ (point)))
(defun sh-prev-stmt ()
(not (bobp))
going)
;; Do a backward-sexp if possible, else backup bit by bit...
- (if (sh-safe-backward-sexp)
+ (if (sh-safe-forward-sexp -1)
(progn
(if (looking-at sh-special-keywords)
(progn
(setq found nil))
(or found
(sh-debug "Did not find prev stmt.")))
- found
- )))
+ found)))
(defun sh-get-word ()
;; Possible return values:
;; nil - nothing
;; a string - possibly a keyword
- ;;
+ ;;
(if (bolp)
nil
(let ((going t)
(found nil))
(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))
+ (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))))
+ (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)
+ (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))
- ))
+ (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)
- ))
+ (char-to-string c)
+ ))
)))
(defun sh-this-is-a-continuation ()
"Return non-nil if current line is a continuation of previous line."
- (let ((result nil)
- bol eol state)
- (save-excursion
- (if (and (zerop (forward-line -1))
- (looking-at ".*\\\\$"))
- (progn
- (setq bol (point))
- (end-of-line)
- (setq eol (point))
- (setq state (parse-partial-sexp bol eol nil nil nil t))
- (unless (nth 4 state)
- (setq result t))
- )))))
+ (save-excursion
+ (and (zerop (forward-line -1))
+ (looking-at ".*\\\\$")
+ (not (nth 4 (parse-partial-sexp (match-beginning 0) (match-end 0)
+ nil nil nil t))))))
(defun sh-get-kw (&optional where and-move)
"Return first word of line from WHERE.
(goto-char where))
(prog1
(buffer-substring (point)
- (progn (skip-chars-forward "^ \t\n;")(point)))
+ (progn (skip-chars-forward "^ \t\n;")(point)))
(unless and-move
- (goto-char start)))
- ))
+ (goto-char start)))))
(defun sh-find-prev-matching (open close &optional depth)
"Find a matching token for a set of opening and closing keywords.
(sh-debug "found close - depth = %d" depth))
(t
))))
- (error nil))
+ (error nil))
(if (eq depth 0)
prev ;; (point)
nil)
(/ (- sh-basic-offset) 2))
(t
(if ignore-error
- (progn
- (message "Don't know how to handle %s's value of %s" var val)
- 0)
- (error "Don't know how to handle %s's value of %s" var val))
+ (progn
+ (message "Don't know how to handle %s's value of %s" var val)
+ 0)
+ (error "Don't know how to handle %s's value of %s" var val))
))))
(defun sh-set-var-value (var value &optional no-symbol)
(defun sh-calculate-indent (&optional info)
"Return the indentation for the current line.
If INFO is supplied it is used, else it is calculated from current line."
- (let (
- (ofs 0)
+ (let ((ofs 0)
(base-value 0)
elt a b var val)
(or info
(setq info (sh-get-indent-info)))
- (if (null info)
- nil
+ (when info
(while info
(sh-debug "info: %s ofs=%s" info ofs)
(setq elt (car info))
(cond
- ((stringp elt)
- ;; do nothing?
- )
+ ((stringp elt)) ;; do nothing?
((listp elt)
(setq a (car (car info)))
(setq b (nth 1 (car info)))
;; no indentation
;; set info to nil so we stop immediately
(setq base-value nil ofs nil info nil))
- ((eq val t)
- ;; indent as normal line
- (setq ofs 0))
+ ((eq val t) (setq ofs 0)) ;; indent as normal line
(t
;; The following assume the (t POS) come first!
(setq ofs val base-value 0)
- (setq info nil) ;; ? stop now
- ))
- )
- ((eq a '+)
- (setq ofs (+ ofs val)))
- ((eq a '-)
- (setq ofs (- ofs val)))
+ (setq info nil)))) ;; ? stop now
+ ((eq a '+) (setq ofs (+ ofs val)))
+ ((eq a '-) (setq ofs (- ofs val)))
(t
(error "sh-calculate-indent invalid a a=%s b=%s" a b))))
(t
- (error "sh-calculate-indent invalid elt: a=%s b=%s" a b)))
- )
+ (error "sh-calculate-indent invalid elt: a=%s b=%s" a b))))
(t
- (error "sh-calculate-indent invalid elt %s" elt))
- )
- (sh-debug "a=%s b=%s val=%s base-value=%s ofs=%s"
- a b val base-value ofs)
- (setq info (cdr info))
- )
+ (error "sh-calculate-indent invalid elt %s" elt)))
+ (sh-debug "a=%s b=%s val=%s base-value=%s ofs=%s"
+ a b val base-value ofs)
+ (setq info (cdr info)))
;; return value:
(sh-debug "at end: base-value: %s ofs: %s" base-value ofs)
nil)
((and (numberp base-value)(numberp ofs))
(sh-debug "base (%d) + ofs (%d) = %d"
- base-value ofs (+ base-value ofs))
+ base-value ofs (+ base-value ofs))
(+ base-value ofs)) ;; return value
(t
(error "sh-calculate-indent: Help. base-value=%s ofs=%s"
base-value ofs)
- nil))
- )))
+ nil)))))
(defun sh-indent-line ()
"Indent the current line."
(interactive)
- (sh-must-be-shell-mode)
(let ((indent (sh-calculate-indent)) shift-amt beg end
(pos (- (point-max) (point))))
- (if indent
- (progn
- (beginning-of-line)
- (setq beg (point))
- (skip-chars-forward " \t")
- (setq shift-amt (- indent (current-column)))
- (if (zerop shift-amt)
- nil
- (delete-region beg (point))
- (indent-to indent))
- ;; If initial point was within line's indentation,
- ;; position after the indentation. Else stay at same point in text.
- (if (> (- (point-max) pos) (point))
- (goto-char (- (point-max) pos)))
- ))))
+ (when indent
+ (beginning-of-line)
+ (setq beg (point))
+ (skip-chars-forward " \t")
+ (setq shift-amt (- indent (current-column)))
+ (unless (zerop shift-amt)
+ (delete-region beg (point))
+ (indent-to indent))
+ ;; If initial point was within line's indentation,
+ ;; position after the indentation. Else stay at same point in text.
+ (if (> (- (point-max) pos) (point))
+ (goto-char (- (point-max) pos))))))
(defun sh-blink (blinkpos &optional msg)
(goto-char blinkpos)
(message msg)
(sit-for blink-matching-delay))
- (message msg)
- ))
+ (message msg)))
(defun sh-show-indent (arg)
"Show the how the currently line would be indented.
(sh-must-support-indent)
(let* ((info (sh-get-indent-info))
(var (sh-get-indent-var-for-line info))
- val msg
- (curr-indent (current-indentation))
- )
+ (curr-indent (current-indentation))
+ val msg)
(if (stringp var)
(message (setq msg var))
(setq val (sh-calculate-indent info))
ival sval diff new-val
(no-symbol arg)
(curr-indent (current-indentation)))
- (cond
- ((stringp var)
- (message (format "Cannot learn line - %s" var)))
- ((eq var 'sh-indent-comment)
- ;; This is arbitrary...
- ;; - if curr-indent is 0, set to curr-indent
- ;; - else if it has the indentation of a "normal" line,
- ;; then set to t
- ;; - else set to curr-indent.
- (setq sh-indent-comment
- (if (= curr-indent 0)
- 0
- (let* ((sh-indent-comment t)
- (val2 (sh-calculate-indent info)))
- (if (= val2 curr-indent)
- t
- curr-indent))))
- (message "%s set to %s" var (symbol-value var))
- )
- ((numberp (setq sval (sh-var-value var)))
- (setq ival (sh-calculate-indent info))
- (setq diff (- curr-indent ival))
-
- (sh-debug "curr-indent: %d ival: %d diff: %d var:%s sval %s"
- curr-indent ival diff var sval)
- (setq new-val (+ sval diff))
+ (cond
+ ((stringp var)
+ (message (format "Cannot learn line - %s" var)))
+ ((eq var 'sh-indent-comment)
+ ;; This is arbitrary...
+ ;; - if curr-indent is 0, set to curr-indent
+ ;; - else if it has the indentation of a "normal" line,
+ ;; then set to t
+ ;; - else set to curr-indent.
+ (setq sh-indent-comment
+ (if (= curr-indent 0)
+ 0
+ (let* ((sh-indent-comment t)
+ (val2 (sh-calculate-indent info)))
+ (if (= val2 curr-indent)
+ t
+ curr-indent))))
+ (message "%s set to %s" var (symbol-value var))
+ )
+ ((numberp (setq sval (sh-var-value var)))
+ (setq ival (sh-calculate-indent info))
+ (setq diff (- curr-indent ival))
+
+ (sh-debug "curr-indent: %d ival: %d diff: %d var:%s sval %s"
+ curr-indent ival diff var sval)
+ (setq new-val (+ sval diff))
;;; I commented out this because someone might want to replace
;;; a value of `+' with the current value of sh-basic-offset
;;; or vice-versa.
;;; (if (= 0 diff)
;;; (message "No change needed!")
- (sh-set-var-value var new-val no-symbol)
- (message "%s set to %s" var (symbol-value var))
- )
- (t
- (debug)
- (message "Cannot change %s" var))
- ))))
+ (sh-set-var-value var new-val no-symbol)
+ (message "%s set to %s" var (symbol-value var))
+ )
+ (t
+ (debug)
+ (message "Cannot change %s" var))))))
(defun sh-mark-init (buffer)
"Initialize a BUFFER to be used by `sh-mark-line'."
- (let ((main-buffer (current-buffer)))
- (save-excursion
- (set-buffer (get-buffer-create buffer))
- (erase-buffer)
- (occur-mode)
- (setq occur-buffer main-buffer)
- )))
+ (save-excursion
+ (set-buffer (get-buffer-create buffer))
+ (erase-buffer)
+ (occur-mode)
+ ))
(defun sh-mark-line (message point buffer &optional add-linenum occur-point)
If OCCUR-POINT is non-nil then the line is marked as a new occurrence
so that `occur-next' and `occur-prev' will work."
(let ((m1 (make-marker))
- (main-buffer (current-buffer))
start
- (line "") )
- (if point
- (progn
- (set-marker m1 point (current-buffer))
- (if add-linenum
- (setq line (format "%d: " (1+ (count-lines 1 point)))))))
+ (line ""))
+ (when point
+ (set-marker m1 point (current-buffer))
+ (if add-linenum
+ (setq line (format "%d: " (1+ (count-lines 1 point))))))
(save-excursion
(if (get-buffer buffer)
(set-buffer (get-buffer buffer))
(set-buffer (get-buffer-create buffer))
(occur-mode)
- (setq occur-buffer main-buffer)
)
(goto-char (point-max))
(setq start (point))
(setq occur-point (point)))
(insert message)
(if point
- (put-text-property start (point) 'mouse-face 'highlight))
+ (add-text-properties
+ start (point)
+ '(mouse-face highlight
+ help-echo "mouse-2: go to the line where I learned this")))
(insert "\n")
(if point
(progn
- (put-text-property start (point) 'occur m1)
+ (put-text-property start (point) 'occur-target m1)
(if occur-point
- (put-text-property occur-point (1+ occur-point)
- 'occur-point t))
+ (put-text-property start occur-point
+ 'occur-match t))
))
)))
;; Is this really worth having?
(defvar sh-learned-buffer-hook nil
"*An abnormal hook, called with an alist of learned variables.")
-;;; Example of how to use sh-learned-buffer-hook
-;;
+;; Example of how to use sh-learned-buffer-hook
+;;
;; (defun what-i-learned (list)
;; (let ((p list))
;; (save-excursion
;; (setq p (cdr p)))
;; (insert ")\n")
;; )))
-;;
+;;
;; (add-hook 'sh-learned-buffer-hook 'what-i-learned)
(while (< (point) (point-max))
(setq linenum (1+ linenum))
-;; (if (zerop (% linenum 10))
- (message "line %d" linenum)
-;; )
+ ;; (if (zerop (% linenum 10))
+ (message "line %d" linenum)
+ ;; )
(unless (looking-at "\\s-*$") ;; ignore empty lines!
(let* ((sh-indent-comment t) ;; info must return default indent
(info (sh-get-indent-info))
(setq diff (- curr-indent ival))
(setq new-val (+ sval diff))
(sh-set-var-value var new-val 'no-symbol)
- (unless (looking-at "\\s-*#");; don't learn from comments
+ (unless (looking-at "\\s-*#") ;; don't learn from comments
(if (setq previous-set-info (assoc var learned-var-list))
(progn
;; it was already there, is it same value ?
(unless (= curr-indent (sh-calculate-indent info))
;; this is not the default indentation
(setq comments-always-default nil)
- (if comment-col;; then we have see one before
+ (if comment-col ;; then we have see one before
(or (eq comment-col curr-indent)
- (setq comment-col t));; seen a different one
+ (setq comment-col t)) ;; seen a different one
(setq comment-col curr-indent))
- ))
- (t
+ ))
+ (t
(sh-debug "Cannot learn this line!!!")
))
(sh-debug
- "at %s learned-var-list is %s" (point) learned-var-list)
+ "at %s learned-var-list is %s" (point) learned-var-list)
))
(forward-line 1)
) ;; while
(format "Suggested sh-basic-offset: %d" suggested))
nil out-buffer))))
-
+
(setq learned-var-list
(append (list (list 'sh-indent-comment comment-col (point-max)))
- learned-var-list))
+ learned-var-list))
(setq sh-indent-comment comment-col)
(let ((name (buffer-name))
- (lines (if (and (eq (point-min) 1)
- (eq (point-max) (1+ (buffer-size))))
- ""
- (format "lines %d to %d of "
- (1+ (count-lines 1 (point-min)))
- (1+ (count-lines 1 (point-max))))))
- )
+ (lines (if (and (eq (point-min) 1)
+ (eq (point-max) (1+ (buffer-size))))
+ ""
+ (format "lines %d to %d of "
+ (1+ (count-lines 1 (point-min)))
+ (1+ (count-lines 1 (point-max))))))
+ )
(sh-mark-line "\nLearned variable settings:" nil out-buffer)
(if arg
;; Set learned variables to symbolic rather than numeric
;; values where possible.
- (progn
- (let ((p (reverse learned-var-list))
- var val)
- (while p
- (setq var (car (car p)))
- (setq val (nth 1 (car p)))
- (cond
- ((eq var 'sh-basic-offset)
- )
- ((numberp val)
- (sh-set-var-value var val))
- (t
- ))
- (setq p (cdr p))
- ))))
- (let ((p (reverse learned-var-list))
- var)
- (while p
- (setq var (car (car p)))
+ (dolist (learned-var (reverse learned-var-list))
+ (let ((var (car learned-var))
+ (val (nth 1 learned-var)))
+ (when (and (not (eq var 'sh-basic-offset))
+ (numberp val))
+ (sh-set-var-value var val)))))
+ (dolist (learned-var (reverse learned-var-list))
+ (let ((var (car learned-var)))
(sh-mark-line (format " %s %s" var (symbol-value var))
- (nth 2 (car p)) out-buffer)
- (setq p (cdr p))))
+ (nth 2 learned-var) out-buffer)))
(save-excursion
- (set-buffer out-buffer)
- (goto-char (point-min))
- (insert
- (format "Indentation values for buffer %s.\n" name)
- (format "%d indentation variable%s different values%s\n\n"
- num-diffs
- (if (= num-diffs 1)
- " has" "s have")
- (if (zerop num-diffs)
- "." ":"))
- )))
+ (set-buffer out-buffer)
+ (goto-char (point-min))
+ (insert
+ (format "Indentation values for buffer %s.\n" name)
+ (format "%d indentation variable%s different values%s\n\n"
+ num-diffs
+ (if (= num-diffs 1)
+ " has" "s have")
+ (if (zerop num-diffs)
+ "." ":"))
+ )))
;; Are abnormal hooks considered bad form?
(run-hook-with-args 'sh-learned-buffer-hook learned-var-list)
(if (or sh-popup-occur-buffer (> num-diffs 0))
reasonable choices
nil - we couldn't find a reasonable one."
(let* ((max (1- (length vec)))
- (i 1)
- (totals (make-vector max 0))
- (return nil)
- j)
+ (i 1)
+ (totals (make-vector max 0))
+ (return nil)
+ j)
(while (< i max)
(aset totals i (+ (aref totals i) (* 4 (aref vec i))))
(setq j (/ i 2))
(aset totals i (+ (aref totals i) (aref vec (/ i 2)))))
(if (< (* i 2) max)
(aset totals i (+ (aref totals i) (aref vec (* i 2)))))
- (setq i (1+ i))
- )
+ (setq i (1+ i)))
+
(let ((x nil)
(result nil)
tot sum p)
(setq x (append x (list (cons i (aref totals i))))))
(setq i (1+ i)))
- (setq x (sort x (lambda (a b)
- (> (cdr a)(cdr b)))))
+ (setq x (sort x (lambda (a b) (> (cdr a) (cdr b)))))
(setq tot (apply '+ (append totals nil)))
(sh-debug (format "vec: %s\ntotals: %s\ntot: %d"
- vec totals tot))
+ vec totals tot))
(cond
((zerop (length x))
(message "no values!")) ;; we return nil
((= (length x) 1)
(message "only value is %d" (car (car x)))
- (setq result (car (car x)))) ;; return single value
+ (setq result (car (car x)))) ;; return single value
((> (cdr (car x)) (/ tot 2))
;; 1st is > 50%
(message "basic-offset is probably %d" (car (car x)))
(car (car x)))
;; result is nil here
))
- result
- )))
-
-
-(defun sh-do-nothing (a b c)
- ;; checkdoc-params: (a b c)
- "A dummy function to prevent font-lock from re-fontifying a change.
-Otherwise, we fontify something and font-lock overwrites it."
- )
-
-;; The default font-lock-unfontify-region-function removes
-;; syntax-table properties, and so removes our information.
-(defun sh-font-lock-unfontify-region-function (beg end)
- (let* ((modified (buffer-modified-p)) (buffer-undo-list t)
- (inhibit-read-only t) (inhibit-point-motion-hooks t)
- before-change-functions after-change-functions
- deactivate-mark buffer-file-name buffer-file-truename)
- (remove-text-properties beg end '(face nil))
- (when (and (not modified) (buffer-modified-p))
- (set-buffer-modified-p nil))))
-
-(defun sh-set-char-syntax (where new-prop)
- "Set the character's syntax table property at WHERE to be NEW-PROP."
- (or where
- (setq where (point)))
- (let ((font-lock-fontify-region-function 'sh-do-nothing))
- (put-text-property where (1+ where) 'syntax-table new-prop)
- (add-text-properties where (1+ where)
- '(face sh-st-face rear-nonsticky t))
- ))
-
-
-(defun sh-check-paren-in-case ()
- "Make syntax class of case label's right parenthesis not close parenthesis.
-If this parenthesis is a case alternative, set its syntax class to a word."
- (let ((start (point))
- state prev-line)
- ;; First test if this is a possible candidate, the first "(" or ")"
- ;; on the line; then, if go, check prev line is ;; or case.
- (save-excursion
- (beginning-of-line)
- ;; stop at comment or when depth becomes -1
- (setq state (parse-partial-sexp (point) start -1 nil nil t))
- (if (and
- (= (car state) -1)
- (= (point) start)
- (setq prev-line (sh-prev-line nil)))
- (progn
- (goto-char prev-line)
- (beginning-of-line)
- ;; (setq case-stmt-start (point))
- ;; (if (looking-at "\\(^\\s-*case[^-a-z0-9_]\\|[^#]*;;\\s-*$\\)")
- (if (sh-search-word "\\(case\\|;;\\)" start)
- (sh-set-char-syntax (1- start) sh-special-syntax)
- ))))))
-
-(defun sh-electric-rparen ()
- "Insert a right parenthesis and check if it is a case alternative.
-If so, its syntax class is set to word, and its text property
-is set to have face `sh-st-face'."
- (interactive)
- (insert ")")
- (if sh-electric-rparen-needed-here
- (sh-check-paren-in-case)))
-
-(defun sh-electric-hash ()
- "Insert a hash, but check it is preceded by \"$\".
-If so, it is given a syntax type of comment.
-Its text property has face `sh-st-face'."
- (interactive)
- (let ((pos (point)))
- (insert "#")
- (if (eq (char-before pos) ?$)
- (sh-set-char-syntax pos sh-st-punc))))
-
-(defun sh-electric-less (arg)
- "Insert a \"<\" and see if this is the start of a here-document.
-If so, the syntax class is set so that it will not be automatically
-reindented.
-Argument ARG if non-nil disables this test."
- (interactive "*P")
- (let ((p1 (point)) p2 p3)
- (sh-maybe-here-document arg) ;; call the original fn in sh-script.el.
- (setq p2 (point))
- (if (/= (+ p1 (prefix-numeric-value arg)) p2)
- (save-excursion
- (forward-line 1)
- (end-of-line)
- (setq p3 (point))
- (sh-set-here-doc-region p2 p3))
- )))
-
-(defun sh-set-here-doc-region (start end)
- "Mark a here-document from START to END so that it will not be reindented."
- (interactive "r")
- ;; Make the whole thing have syntax type word...
- ;; That way sexp movement doens't worry about any parentheses.
- ;; A disadvantage of this is we can't use forward-word within a
- ;; here-doc, which is annoying.
- (let ((font-lock-fontify-region-function 'sh-do-nothing))
- (put-text-property start end 'syntax-table sh-here-doc-syntax)
- (put-text-property start end 'face 'sh-heredoc-face)
- (put-text-property (1- end) end 'rear-nonsticky t)
- (put-text-property start (1+ start) 'front-sticky t)
- ))
-
-(defun sh-remove-our-text-properties ()
- "Remove text properties relating to right parentheses and here documents."
- (interactive)
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (let ((plist (text-properties-at (point)))
- (next-change
- (or (next-single-property-change (point) 'syntax-table
- (current-buffer) )
- (point-max))))
- ;; Process text from point to NEXT-CHANGE...
- (if (get-text-property (point) 'syntax-table)
- (progn
- (sh-debug "-- removing props from %d to %d --"
- (point) next-change)
- (remove-text-properties (point) next-change
- '(syntax-table nil))
- (remove-text-properties (point) next-change '(face nil))
- ))
- (goto-char next-change)))
- ))
-
-;; (defun sh-search-word (word &optional limit)
-;; "Search forward for regexp WORD occurring as a word not in string nor comment.
-;; If found, returns non nil with the match available in \(match-string 2\).
-;; Yes 2, not 1, since we build a regexp to guard against false matches
-;; such as matching \"a-case\" when we are searching for \"case\".
-;; If not found, it returns nil.
-;; The search maybe limited by optional argument LIMIT."
-;; (interactive "sSearch for: ")
-;; (let ((found nil)
-;; ;; Cannot use \\b here since it matches "-" and "_"
-;; (regexp (sh-mkword-regexp word))
-;; start state where)
-;; (setq start (point))
-;; (while (and (setq start (point))
-;; (not found)
-;; (re-search-forward regexp limit t))
-;; ;; Found str; check it is not in a comment or string.
-;; (setq state
-;; ;; Stop on comment:
-;; (parse-partial-sexp start (point) nil nil nil 'syntax_table))
-;; (if (setq where (nth 8 state))
-;; ;; in comment or string
-;; (if (= where -1)
-;; (setq found (point))
-;; (if (eq (char-after where) ?#)
-;; (end-of-line)
-;; (goto-char where)
-;; (unless (sh-safe-forward-sexp)
-;; ;; If the above fails we must either give up or
-;; ;; move forward and try again.
-;; (forward-line 1))
-;; ))
-;; ;; not in comment or string, so accept it
-;; (setq found (point))
-;; ))
-;; found
-;; ))
-
-(defun sh-search-word (word &optional limit)
- "Search forward for regexp WORD occurring as a word not in string nor comment.
-If found, returns non-nil, with the match available in \(match-string 2\).
-Yes, that is 2, not 1.
-If not found, it returns nil.
-The search may be limited by optional argument LIMIT."
- (interactive "sSearch for: ")
- (let ((found nil)
- start state where match)
- (setq start (point))
- (while (and (not found)
- (re-search-forward word limit t))
- (setq match (match-data))
- ;; Found the word as a string; check it occurs as a word.
- (when (and (or (= (match-beginning 0) (point-min))
- (save-excursion
- (goto-char (1- (match-beginning 0)))
- (looking-at "[^-a-z0-9_]")))
- (or (= (point) (point-max))
- (looking-at "[^-a-z0-9_]")))
- ;; Check it is not in a comment or string.
- (setq state
- ;; Stop on comment:
- (parse-partial-sexp start (point) nil nil nil 'syntax_table))
- (if (setq where (nth 8 state))
- ;; in comment or string
- (if (= where -1)
- (setq found (point))
- (if (eq (char-after where) ?#)
- (end-of-line)
- (goto-char where)
- (unless (sh-safe-forward-sexp)
- ;; If the above fails we must either give up or
- ;; move forward and try again.
- (forward-line 1))))
- ;; not in comment or string, so accept it
- (setq found (point)))
- (setq start (point))))
- (when found
- (set-match-data match)
- (goto-char (1- (match-beginning 0)))
- (looking-at (sh-mkword-regexp word))
- (goto-char found))
- found
- ))
-
-
-(defun sh-scan-case ()
- "Scan a case statement for right parens belonging to case alternatives.
-Mark each as having syntax `sh-special-syntax'.
-Called from scan-buff. If ok, return non-nil."
- (let (end
- state
- (depth 1) ;; we are called at a "case"
- (start (point))
- (return t))
- ;; We enter here at a case statement
- ;; First, find limits of the case.
- (while (and (> depth 0)
- (sh-search-word "\\(case\\|esac\\)"))
- (if (equal (match-string 2) "case")
- (setq depth (1+ depth))
- (setq depth (1- depth))))
- ;; (message "end of search for esac at %d depth=%d" (point) depth)
- (setq end (point))
- (goto-char start)
- ;; if we found the esac, then fix all appropriate ')'s in the region
- (if (zerop depth)
- (progn
- (while (< (point) end)
- ;; search for targetdepth of -1 meaning extra right paren
- (setq state (parse-partial-sexp (point) end -1 nil nil nil))
- (if (and (= (car state) -1)
- (= (char-before) ?\)))
- (progn
- ;; (message "At %d state is %s" (point) state)
- ;; (message "Fixing %d" (point))
- (sh-set-char-syntax (1- (point)) sh-special-syntax)
- ;; we could advance to the next ";;" perhaps
- )
- ;; (message "? Not found at %d" (point)) ; ok, could be "]"
- ))
- (goto-char end))
- (message "No matching esac for case at %d" start)
- (setq return nil)
- )
- return
- ))
-
-
-;; FIXME: This loses big time on very large files (such as CVS' sanity.sh).
-(defun sh-scan-buffer ()
- "Scan a sh buffer for case statements and here-documents.
-
-For each case alternative found, mark its \")\" with a text property
-so that its syntax class is no longer a close parenthesis character.
-
-Each here-document is also marked so that it is effectively immune
-from indentation changes."
- ;; Do not call this interactively, call `sh-rescan-buffer' instead.
- (sh-must-be-shell-mode)
- (let ((n 0)
- (initial-buffer-modified-p (buffer-modified-p))
- start end where label ws)
- (save-excursion
- (goto-char (point-min))
- ;; 1. Scan for ")" in case statements.
- (while (and ;; (re-search-forward "^[^#]*\\bcase\\b" nil t)
- (sh-search-word "\\(case\\|esac\\)")
- ;; (progn (message "Found a case at %d" (point)) t)
- (sh-scan-case)))
- ;; 2. Scan for here docs
- (goto-char (point-min))
- ;; while (re-search-forward "<<\\(-?\\)\\(\\s-*\\)\\(.*\\)$" nil t)
- (while (re-search-forward "<<\\(-?\\)" nil t)
- (unless (sh-in-comment-or-string (match-beginning 0))
- ;; (setq label (match-string 3))
- (setq label (sh-get-word))
- (if (string= (match-string 1) "-")
- ;; if <<- then we allow whitespace
- (setq ws "\\s-*")
- ;; otherwise we don't
- (setq ws ""))
- (while (string-match "['\"\\]" label)
- (setq label (replace-match "" nil nil label)))
- (if (setq n (string-match "\\s-+$" label))
- (setq label (substring label 0 n)))
- (forward-line 1)
- ;; the line containing the << could be continued...
- (while (sh-this-is-a-continuation)
- (forward-line 1))
- (setq start (point))
- (if (re-search-forward (concat "^" ws (regexp-quote label)
- "\\s-*$")
- nil t)
- (sh-set-here-doc-region start (point))
- (sh-debug "missing here-doc delimiter `%s'" label))))
- ;; 3. Scan for $# -- make the "#" a punctuation not a comment
- (goto-char (point-min))
- (let (state)
- (while (and (not (eobp))
- (setq state (parse-partial-sexp
- (1+ (point))(point-max) nil nil nil t))
- (nth 4 state))
- (goto-char (nth 8 state))
- (sh-debug "At %d %s" (point) (eq (char-before) ?$))
- (if (eq (char-before) ?$)
- (sh-set-char-syntax (point) sh-st-punc) ;; not a comment!
- (end-of-line) ;; if this *was* a comment, ignore rest of line!
- )))
- ;; 4. Hide these changes from making a previously unmodified
- ;; buffer into a modified buffer.
- (if sh-debug
- (if initial-buffer-modified-p
- (message "buffer was initially modified")
- (message
- "buffer not initially modified - so clearing modified flag")))
- (set-buffer-modified-p initial-buffer-modified-p)
- )))
-
-(defun sh-rescan-buffer ()
- "Rescan the buffer for case alternative parentheses and here documents."
- (interactive)
- (if (eq major-mode 'sh-mode)
- (let ((inhibit-read-only t))
- (sh-remove-our-text-properties)
- (message "Re-scanning buffer...")
- (sh-scan-buffer)
- (message "Re-scanning buffer...done")
- )))
+ result)))
;; ========================================================================
(list
(read-from-minibuffer "Name for this style? " )
(not current-prefix-arg)))
- (let ((slist (list name))
- (p sh-var-list)
- var style)
- (while p
- (setq var (car p))
- (setq slist (append slist (list (cons var (symbol-value var)))))
- (setq p (cdr p)))
- (if (setq style (assoc name sh-styles-alist))
- (if (or (not confirm-overwrite)
- (y-or-n-p "This style exists. Overwrite it? "))
- (progn
- (message "Updating style %s" name)
- (setcdr style (cdr slist)))
- (message "Not changing style %s" name))
+ (let ((slist (cons name
+ (mapcar (lambda (var) (cons var (symbol-value var)))
+ sh-var-list)))
+ (style (assoc name sh-styles-alist)))
+ (if style
+ (if (and confirm-overwrite
+ (not (y-or-n-p "This style exists. Overwrite it? ")))
+ (message "Not changing style %s" name)
+ (message "Updating style %s" name)
+ (setcdr style (cdr slist)))
(message "Creating new style %s" name)
- (setq sh-styles-alist (append sh-styles-alist
- (list slist)))
- )))
+ (push slist sh-styles-alist))))
(defun sh-load-style (name)
"Set shell indentation values for this buffer from those in style NAME."
(let ((sl (assoc name sh-styles-alist)))
(if (null sl)
(error "sh-load-style - style %s not known" name)
- (setq sl (cdr sl))
- (while sl
- (set (car (car sl)) (cdr (car sl)))
- (setq sl (cdr sl))
- ))))
+ (dolist (var (cdr sl))
+ (set (car var) (cdr var))))))
(defun sh-save-styles-to-buffer (buff)
"Save all current styles in elisp to buffer BUFF.
This is always added to the end of the buffer."
(interactive (list
- (read-from-minibuffer "Buffer to save styles in? " "*scratch*")))
- ;; This is an attempt to sort of pretty print it...
- (save-excursion
- (set-buffer (get-buffer-create buff))
+ (read-from-minibuffer "Buffer to save styles in? " "*scratch*")))
+ (with-current-buffer (get-buffer-create buff)
(goto-char (point-max))
(insert "\n")
- (let ((p sh-styles-alist) q)
- (insert "(setq sh-styles-alist '(\n")
- (while p
- (setq q (car p))
- (insert " ( " (prin1-to-string (car q)) "\n")
- (setq q (cdr q))
- (while q
- (insert " "(prin1-to-string (car q)) "\n")
- (setq q (cdr q)))
- (insert " )\n")
- (setq p (cdr p))
- )
- (insert "))\n")
- )))
-
+ (pp `(setq sh-styles-alist ',sh-styles-alist) (current-buffer))))
\f
< "default:" \n
> _ \n
resume:
- < < "endsw")
+ < < "endsw" \n)
(es)
(rc "expression: "
> "switch( " str " ) {" \n
"case *" > \n
> _ \n
resume:
- ?} > )
+ ?\} > \n)
(sh "expression: "
> "case " str " in" \n
> (read-string "pattern: ")
- '(sh-electric-rparen)
+ (propertize ")" 'syntax-table sh-st-punc)
\n
> _ \n
";;" \n
( "other pattern, %s: "
- > str '(sh-electric-rparen) \n
+ > str (propertize ")" 'syntax-table sh-st-punc) \n
> _ \n
";;" \n)
- > "*" '(sh-electric-rparen) \n
+ > "*" (propertize ")" 'syntax-table sh-st-punc) \n
> _ \n
resume:
- "esac" > ))
+ "esac" > \n))
(define-skeleton sh-for
"Insert a for loop. See `sh-feature'."
4 " ( "
6 " )"
15 '<
- 16 "end"
- )
+ 16 "end")
(es eval sh-modify rc
4 " = ")
(rc eval sh-modify sh
2 "for( "
6 " ) {"
- 15 ?} )
+ 15 ?\} )
(sh "Index variable: "
> "for " str " in " _ "; do" \n
> _ | ?$ & (sh-remember-variable str) \n
- "done" > ))
+ "done" > \n))
"while( $" str " <= " (read-string "upper limit: ") " )" \n
> _ ?$ str \n
"@ " str "++" \n
- < "end")
+ < "end" \n)
(es eval sh-modify rc
4 " =")
(ksh88 "Index variable: "
(read-string "upper limit: ")
" )); do" \n
> _ ?$ (sh-remember-variable str) > \n
- "done" > )
+ "done" > \n)
(posix "Index variable: "
> str "=1" \n
"while [ $" str " -le "
" ]; do" \n
> _ ?$ str \n
str ?= (sh-add (sh-remember-variable str) 1) \n
- "done" > )
+ "done" > \n)
(rc "Index variable: "
> "for( " str " in" " `{awk 'BEGIN { for( i=1; i<="
(read-string "upper limit: ")
"; i++ ) print i }'`}) {" \n
> _ ?$ (sh-remember-variable str) \n
- ?} >)
+ ?\} > \n)
(sh "Index variable: "
> "for " str " in `awk 'BEGIN { for( i=1; i<="
(read-string "upper limit: ")
"; i++ ) print i }'`; do" \n
> _ ?$ (sh-remember-variable str) \n
- "done" > ))
+ "done" > \n))
(defun sh-shell-initialize-variables ()
(ksh88 "name: "
"function " str " {" \n
> _ \n
- < "}")
+ < "}" \n)
(rc eval sh-modify ksh88
- 1 "fn ")
+ 1 "fn ")
(sh ()
"() {" \n
> _ \n
- < "}"))
+ < "}" \n))
< "else" \n
> _ \n
resume:
- < "endif")
+ < "endif" \n)
(es "condition: "
- > "if { " str " } {" \n
- > _ \n
- ( "other condition, %s: "
- "} { " str " } {" > \n
- > _ \n)
- "} {" > \n
- > _ \n
- resume:
- ?} > )
+ > "if { " str " } {" \n
+ > _ \n
+ ( "other condition, %s: "
+ "} { " str " } {" > \n
+ > _ \n)
+ "} {" > \n
+ > _ \n
+ resume:
+ ?\} > \n)
(rc "condition: "
- > "if( " str " ) {" \n
- > _ \n
- ( "other condition, %s: "
- "} else if( " str " ) {" > \n
- > _ \n)
- "} else {" > \n
- > _ \n
- resume:
- ?} >
- )
+ > "if( " str " ) {" \n
+ > _ \n
+ ( "other condition, %s: "
+ "} else if( " str " ) {" > \n
+ > _ \n)
+ "} else {" > \n
+ > _ \n
+ resume:
+ ?\} > \n)
(sh "condition: "
'(setq input (sh-feature sh-test))
> "if " str "; then" \n
> _ \n
( "other condition, %s: "
- > "elif " str "; then" > \n
- > \n)
- "else" > \n
+ > "elif " str "; then" > \n
+ > \n)
+ "else" > \n
> \n
resume:
- "fi" > ))
+ "fi" > \n))
(es nil
> "forever {" \n
> _ \n
- ?} > )
+ ?\} > \n)
(zsh "factor: "
> "repeat " str "; do" > \n
- > \n
- "done" > ))
+ > \n
+ "done" > \n))
;;;(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat))
(ksh88 "Index variable: "
> "select " str " in " _ "; do" \n
> ?$ str \n
- "done" > )
- (bash eval sh-append ksh88)
- )
+ "done" > \n)
+ (bash eval sh-append ksh88))
;;;(put 'sh-select 'menu-enable '(sh-feature sh-select))
(not (bolp))
?\n)
"exit:\n"
- "rm $tmp* >&/dev/null" >)
+ "rm $tmp* >&/dev/null" > \n)
(es (file-name-nondirectory (buffer-file-name))
> "local( signals = $signals sighup sigint; tmp = /tmp/" str
".$pid ) {" \n
> "rm $tmp^* >[2]/dev/null" \n
"throw $e" \n
"} {" > \n
- _ \n
- ?} > \n
- ?} > )
+ _ \n
+ ?\} > \n
+ ?\} > \n)
(ksh88 eval sh-modify sh
7 "EXIT")
(rc (file-name-nondirectory (buffer-file-name))
> "tmp = /tmp/" str ".$pid" \n
- "fn sigexit { rm $tmp^* >[2]/dev/null }")
+ "fn sigexit { rm $tmp^* >[2]/dev/null }" \n)
(sh (file-name-nondirectory (buffer-file-name))
> "TMP=${TMPDIR:-/tmp}/" str ".$$" \n
- "trap \"rm $TMP* 2>/dev/null\" " ?0))
+ "trap \"rm $TMP* 2>/dev/null\" " ?0 \n))
'(setq input (sh-feature sh-test))
> "until " str "; do" \n
> _ \n
- "done" > ))
+ "done" > \n))
;;;(put 'sh-until 'menu-enable '(sh-feature sh-until))
3 "while( "
5 " )"
10 '<
- 11 "end" )
+ 11 "end")
(es eval sh-modify sh
3 "while { "
5 " } {"
- 10 ?} )
+ 10 ?\} )
(rc eval sh-modify sh
3 "while( "
5 " ) {"
- 10 ?} )
+ 10 ?\} )
(sh "condition: "
'(setq input (sh-feature sh-test))
> "while " str "; do" \n
> _ \n
- "done" > ))
+ "done" > \n))
resume:
< < "endsw" \n
"shift" \n
- < "end")
+ < "end" \n)
(ksh88 eval sh-modify sh
16 "print"
18 "${0##*/}"
v2 "\"$OPTARG\"")
(setq v1 (cdr v1)
v2 nil)))
- > str "|+" str '(sh-electric-rparen) \n
+ > str "|+" str (propertize ")" 'syntax-table sh-st-punc) \n
> _ v2 \n
> ";;" \n)
- > "*" '(sh-electric-rparen) \n
+ > "*" (propertize ")" 'syntax-table sh-st-punc) \n
> "echo" " \"usage: " "`basename $0`"
" [+-" '(setq v1 (point)) str
'(save-excursion
(if (and (sequencep v1) (length v1)) "] " "} ")
"[--] ARGS...\"" \n
"exit 2" > \n
- "esac" >
- \n "done"
- > \n
- "shift " (sh-add "OPTIND" -1)))
+ "esac" >
+ \n "done"
+ > \n
+ "shift " (sh-add "OPTIND" -1) \n))
(defun sh-maybe-here-document (arg)
- "Inserts self. Without prefix, following unquoted `<' inserts here document.
+ "Insert self. Without prefix, following unquoted `<' inserts here document.
The document is bounded by `sh-here-document-word'."
(interactive "*P")
(self-insert-command (prefix-numeric-value arg))