(ksh88 . jsh)
(oash . sh)
(pdksh . ksh88)
+ (mksh . pdksh)
(posix . sh)
(tcsh . csh)
(wksh . ksh88)
ksh Korn Shell '93
dtksh CDE Desktop Korn Shell
pdksh Public Domain Korn Shell
+ mksh MirOS BSD Korn Shell
wksh Window Korn Shell
zsh Z Shell
oash SCO OA (curses) Shell
:version "24.4" ; added dash
:group 'sh-script)
-
(defcustom sh-alias-alist
(append (if (eq system-type 'gnu/linux)
'((csh . tcsh)
;; for the time being
'((ksh . ksh88)
(bash2 . bash)
- (sh5 . sh)))
+ (sh5 . sh)
+ ;; Android's system shell
+ ("^/system/bin/sh$" . mksh)))
"Alist for transforming shell names to what they really are.
-Use this where the name of the executable doesn't correspond to the type of
-shell it really is."
- :type '(repeat (cons symbol symbol))
+Use this where the name of the executable doesn't correspond to
+the type of shell it really is. Keys are regular expressions
+matched against the full path of the interpreter. (For backward
+compatibility, keys may also be symbols, which are matched
+against the interpreter's basename. The values are symbols
+naming the shell."
+ :type '(repeat (cons (radio
+ (regexp :tag "Regular expression")
+ (symbol :tag "Basename"))
+ (symbol :tag "Shell")))
:group 'sh-script)
"Non-nil if `sh-shell-variables' is initialized.")
(defun sh-canonicalize-shell (shell)
- "Convert a shell name SHELL to the one we should handle it as."
- (if (string-match "\\.exe\\'" shell)
- (setq shell (substring shell 0 (match-beginning 0))))
- (or (symbolp shell)
- (setq shell (intern shell)))
- (or (cdr (assq shell sh-alias-alist))
- shell))
-
-(defvar sh-shell (sh-canonicalize-shell (file-name-nondirectory sh-shell-file))
+ "Convert a shell name SHELL to the one we should handle it as.
+SHELL is a full path to the shell interpreter; return a shell
+name symbol."
+ (cl-loop
+ with shell = (cond ((string-match "\\.exe\\'" shell)
+ (substring shell 0 (match-beginning 0)))
+ (t shell))
+ with shell-base = (intern (file-name-nondirectory shell))
+ for (key . value) in sh-alias-alist
+ if (and (stringp key) (string-match key shell)) return value
+ if (eq key shell-base) return value
+ finally return shell-base))
+
+(defvar sh-shell (sh-canonicalize-shell sh-shell-file)
"The shell being programmed. This is set by \\[sh-set-shell].")
;;;###autoload(put 'sh-shell 'safe-local-variable 'symbolp)
?~ "_"
?, "_"
?= "."
+ ?\; "."
+ ?| "."
+ ?& "."
?< "."
?> ".")
"The syntax table to use for Shell-Script mode.
(:foreground "tan1" ))
(t
(:weight bold)))
- "Face to show a here-document"
+ "Face to show a here-document."
:group 'sh-indentation)
;; These colors are probably icky. It's just a placeholder though.
(:foreground "magenta"))
(t
(:weight bold)))
- "Face to show quoted execs like ``"
+ "Face to show quoted execs like `blabla`."
:group 'sh-indentation)
(define-obsolete-face-alias 'sh-heredoc-face 'sh-heredoc "22.1")
(defvar sh-heredoc-face 'sh-heredoc)
"Search for a subshell embedded in a string.
Find all the unescaped \" characters within said subshell, remembering that
subshells can nest."
- ;; FIXME: This can (and often does) match multiple lines, yet it makes no
- ;; effort to handle multiline cases correctly, so it ends up being
- ;; rather flaky.
(when (eq ?\" (nth 3 (syntax-ppss))) ; Check we matched an opening quote.
;; bingo we have a $( or a ` inside a ""
(let (;; `state' can be: double-quote, backquote, code.
(state (if (eq (char-before) ?`) 'backquote 'code))
+ (startpos (point))
;; Stacked states in the context.
(states '(double-quote)))
(while (and state (progn (skip-chars-forward "^'\\\\\"`$()" limit)
(`double-quote nil)
(_ (setq state (pop states)))))
(_ (error "Internal error in sh-font-lock-quoted-subshell")))
- (forward-char 1)))))
+ (forward-char 1))
+ (when (< startpos (line-beginning-position))
+ (put-text-property startpos (point) 'syntax-multiline t)
+ (add-hook 'syntax-propertize-extend-region-functions
+ 'syntax-propertize-multiline nil t))
+ )))
(defun sh-is-quoted-p (pos)
\f
;; mode-command and utility functions
+(defun sh-after-hack-local-variables ()
+ (when (assq 'sh-shell file-local-variables-alist)
+ (sh-set-shell (if (symbolp sh-shell)
+ (symbol-name sh-shell)
+ sh-shell))))
+
;;;###autoload
(define-derived-mode sh-mode prog-mode "Shell-script"
"Major mode for editing shell scripts.
((string-match "[.]csh\\>" buffer-file-name) "csh")
((equal (file-name-nondirectory buffer-file-name) ".profile") "sh")
(t sh-shell-file))
- nil nil))
+ nil nil)
+ (add-hook 'hack-local-variables-hook
+ #'sh-after-hack-local-variables nil t))
;;;###autoload
(defalias 'shell-script-mode 'sh-mode)
((equal tok "in") (sh-smie--sh-keyword-in-p))
(t (sh-smie--keyword-p))))
+(defun sh-smie--default-forward-token ()
+ (forward-comment (point-max))
+ (buffer-substring-no-properties
+ (point)
+ (progn (if (zerop (skip-syntax-forward "."))
+ (while (progn (skip-syntax-forward "w_'")
+ (looking-at "\\\\"))
+ (forward-char 2)))
+ (point))))
+
+(defun sh-smie--default-backward-token ()
+ (forward-comment (- (point)))
+ (let ((pos (point))
+ (n (skip-syntax-backward ".")))
+ (if (or (zerop n)
+ (and (eq n -1)
+ (let ((p (point)))
+ (if (eq -1 (% (skip-syntax-backward "\\") 2))
+ t
+ (goto-char p)
+ nil))))
+ (while
+ (progn (skip-syntax-backward "w_'")
+ (or (not (zerop (skip-syntax-backward "\\")))
+ (when (eq ?\\ (char-before (1- (point))))
+ (let ((p (point)))
+ (forward-char -1)
+ (if (eq -1 (% (skip-syntax-backward "\\") 2))
+ t
+ (goto-char p)
+ nil))))))
+ (goto-char (- (point) (% (skip-syntax-backward "\\") 2))))
+ (buffer-substring-no-properties (point) pos)))
+
(defun sh-smie-sh-forward-token ()
(if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)")
(save-excursion
tok))
(t
(let* ((pos (point))
- (tok (smie-default-forward-token)))
+ (tok (sh-smie--default-forward-token)))
(cond
((equal tok ")") "case-)")
((equal tok "(") "case-(")
(goto-char (match-beginning 1))
(match-string-no-properties 1))
(t
- (let ((tok (smie-default-backward-token)))
+ (let ((tok (sh-smie--default-backward-token)))
(cond
((equal tok ")") "case-)")
((equal tok "(") "case-(")
(`(:after . "case-)") (- (sh-var-value 'sh-indent-for-case-alt)
(sh-var-value 'sh-indent-for-case-label)))
((and `(:before . ,_)
- (guard (when sh-indent-after-continuation
- (save-excursion
- (ignore-errors
- (skip-chars-backward " \t")
- (sh-smie--looking-back-at-continuation-p))))))
- ;; After a line-continuation, make sure the rest is indented.
- (let* ((sh-indent-after-continuation nil)
- (indent (smie-indent-calculate))
- (initial (sh-smie--continuation-start-indent)))
- (when (and (numberp indent) (numberp initial)
- (<= indent initial))
- `(column . ,(+ initial sh-indentation)))))
- (`(:before . ,(or `"(" `"{" `"["))
- (if (smie-rule-hanging-p) (smie-rule-parent)))
+ ;; After a line-continuation, make sure the rest is indented.
+ (guard sh-indent-after-continuation)
+ (guard (save-excursion
+ (ignore-errors
+ (skip-chars-backward " \t")
+ (sh-smie--looking-back-at-continuation-p))))
+ (let initial (sh-smie--continuation-start-indent))
+ (guard (let* ((sh-indent-after-continuation nil)
+ (indent (smie-indent-calculate)))
+ (and (numberp indent) (numberp initial)
+ (<= indent initial)))))
+ `(column . ,(+ initial sh-indentation)))
+ (`(:before . ,(or `"(" `"{" `"[" "while" "if" "for" "case"))
+ (if (not (smie-rule-prev-p "&&" "||" "|"))
+ (when (smie-rule-hanging-p)
+ (smie-rule-parent))
+ (unless (smie-rule-bolp)
+ (while (equal "|" (nth 2 (smie-backward-sexp 'halfexp))))
+ `(column . ,(smie-indent-virtual)))))
;; FIXME: Maybe this handling of ;; should be made into
;; a smie-rule-terminator function that takes the substitute ";" as arg.
(`(:before . ,(or `";;" `";&" `";;&"))
(smie-rule-bolp))))
(current-column)
(smie-indent-calculate)))))
- (`(:after . "|") (if (smie-rule-parent-p "|") nil 4))
+ (`(:before . ,(or `"|" `"&&" `"||"))
+ (unless (smie-rule-parent-p token)
+ (smie-backward-sexp token)
+ `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+ (smie-indent-virtual)))))
+
;; Attempt at backward compatibility with the old config variables.
(`(:before . "fi") (sh-var-value 'sh-indent-for-fi))
(`(:before . "done") (sh-var-value 'sh-indent-for-done))
;; tok))
(t
(let* ((pos (point))
- (tok (smie-default-forward-token)))
+ (tok (sh-smie--default-forward-token)))
(cond
;; ((equal tok ")") "case-)")
((and tok (string-match "\\`[a-z]" tok)
;; (goto-char (match-beginning 1))
;; (match-string-no-properties 1))
(t
- (let ((tok (smie-default-backward-token)))
+ (let ((tok (sh-smie--default-backward-token)))
(cond
;; ((equal tok ")") "case-)")
((and tok (string-match "\\`[a-z]" tok)
t))
(if (string-match "\\.exe\\'" shell)
(setq shell (substring shell 0 (match-beginning 0))))
- (setq sh-shell (intern (file-name-nondirectory shell))
- sh-shell (or (cdr (assq sh-shell sh-alias-alist))
- sh-shell))
+ (setq sh-shell (sh-canonicalize-shell shell))
(if insert-flag
(setq sh-shell-file
(executable-set-magic shell (sh-feature sh-shell-arg)
(let ((mksym (lambda (name)
(intern (format "sh-smie-%s-%s"
sh-indent-supported-here name)))))
+ (add-function :around (local 'smie--hanging-eolp-function)
+ (lambda (orig)
+ (if (looking-at "[ \t]*\\\\\n")
+ (goto-char (match-end 0))
+ (funcall orig))))
(smie-setup (symbol-value (funcall mksym "grammar"))
(funcall mksym "rules")
:forward-token (funcall mksym "forward-token")
(sh-make-vars-local))
(message "Indentation setup for shell type %s" sh-shell))
(message "No indentation for this shell type.")
- (setq indent-line-function 'sh-basic-indent-line))
+ (setq-local indent-line-function 'sh-basic-indent-line))
(when font-lock-mode
(setq font-lock-set-defaults nil)
(font-lock-set-defaults)
- (font-lock-fontify-buffer))
+ (font-lock-flush))
(setq sh-shell-process nil)
(run-hooks 'sh-set-shell-hook))