From: Stefan Monnier Date: Fri, 11 May 2012 14:24:50 +0000 (-0400) Subject: * lisp/progmodes/sh-script.el: Use post-self-insert-hook&electric-pair-mode. X-Git-Tag: emacs-24.2.90~471^2~120 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=ff46c759ddf01935f111660d982ec03d83406d24;p=emacs.git * lisp/progmodes/sh-script.el: Use post-self-insert-hook&electric-pair-mode. Provide SMIE-based indentation (not enabled by default yet). (sh-mode-map): Don't bind electric keys. Use electric-pair-mode instead of skeleton-pair. (sh-assignment-regexp): Fit within 80 columns. (sh-indent-supported): Specify actual shell name instead of boolean. (sh--maybe-here-document): New fun, from sh-maybe-here-document. (sh-maybe-here-document): Use it. Make obsolete. (sh-electric-here-document-mode) New minor mode. (sh-mode): Use it. Don't set sh-indent-supported-here here. (sh-smie-sh-grammar, sh-smie--sh-operators, sh-smie--sh-operators-re) (sh-smie--sh-operators-back-re, sh-indent-after-continuation) (sh-smie-rc-grammar, sh-use-smie): New vars. (sh-smie--keyword-p, sh-smie--newline-semi-p, sh-smie--sh-keyword-p) (sh-smie-sh-forward-token, sh-smie--looking-back-at-continuation-p) (sh-smie-sh-backward-token, sh-smie--continuation-start-indent) (sh-smie-sh-rules, sh-smie-rc-rules, sh-smie--sh-keyword-in-p) (sh-smie--rc-after-special-arg-p, sh-smie-rc-backward-token) (sh-smie-sh-rules, sh-smie--rc-newline-semi-p): New functions. (sh-set-shell): Use smie-setup if requested. --- diff --git a/etc/NEWS b/etc/NEWS index 9c7cb834b8d..a584d5943ea 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -90,6 +90,11 @@ character when doing minibuffer filename prompts. * Changes in Specialized Modes and Packages in Emacs 24.2 +** `sh-script' +*** Pairing of parens/quotes uses electric-pair-mode instead of skeleton-pair. +*** `sh-electric-here-document-mode' now controls auto-insertion of here-docs. +*** `sh-use-smie' lets you choose a new indentation and navigation code. + ** reStructuredText mode *** Major merge with upstream development. diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 17d0fcb6427..a70257b2a83 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,5 +1,26 @@ 2012-05-11 Stefan Monnier + * progmodes/sh-script.el: Use post-self-insert-hook&electric-pair-mode. + Provide SMIE-based indentation (not enabled by default yet). + (sh-mode-map): Don't bind electric keys. + Use electric-pair-mode instead of skeleton-pair. + (sh-assignment-regexp): Fit within 80 columns. + (sh-indent-supported): Specify actual shell name instead of boolean. + (sh--maybe-here-document): New fun, from sh-maybe-here-document. + (sh-maybe-here-document): Use it. Make obsolete. + (sh-electric-here-document-mode) New minor mode. + (sh-mode): Use it. Don't set sh-indent-supported-here here. + (sh-smie-sh-grammar, sh-smie--sh-operators, sh-smie--sh-operators-re) + (sh-smie--sh-operators-back-re, sh-indent-after-continuation) + (sh-smie-rc-grammar, sh-use-smie): New vars. + (sh-smie--keyword-p, sh-smie--newline-semi-p, sh-smie--sh-keyword-p) + (sh-smie-sh-forward-token, sh-smie--looking-back-at-continuation-p) + (sh-smie-sh-backward-token, sh-smie--continuation-start-indent) + (sh-smie-sh-rules, sh-smie-rc-rules, sh-smie--sh-keyword-in-p) + (sh-smie--rc-after-special-arg-p, sh-smie-rc-backward-token) + (sh-smie-sh-rules, sh-smie--rc-newline-semi-p): New functions. + (sh-set-shell): Use smie-setup if requested. + * term.el (term-set-escape-char): Properly set term-escape-char. See http://stackoverflow.com/questions/10524656. diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el index f60ce185bc7..a07ecfcb3a4 100644 --- a/lisp/progmodes/sh-script.el +++ b/lisp/progmodes/sh-script.el @@ -326,7 +326,9 @@ shell it really is." (defcustom sh-imenu-generic-expression `((sh - . ((nil "^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\s-*()" 2)))) + . ((nil + "^\\s-*\\(function\\s-+\\)?\\([[:alpha:]_][[:alnum:]_]+\\)\\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") @@ -460,14 +462,6 @@ This is buffer-local in every such buffer.") (define-key map "\C-c+" 'sh-add) (define-key map "\C-\M-x" 'sh-execute-region) (define-key map "\C-c\C-x" 'executable-interpret) - ;; FIXME: Use post-self-insert-hook. - (define-key map "<" 'sh-maybe-here-document) - (define-key map "(" 'skeleton-pair-insert-maybe) - (define-key map "{" 'skeleton-pair-insert-maybe) - (define-key map "[" 'skeleton-pair-insert-maybe) - (define-key map "'" 'skeleton-pair-insert-maybe) - (define-key map "`" 'skeleton-pair-insert-maybe) - (define-key map "\"" 'skeleton-pair-insert-maybe) (define-key map [remap complete-tag] 'comint-dynamic-complete) (define-key map [remap delete-backward-char] @@ -478,10 +472,10 @@ This is buffer-local in every such buffer.") (define-key map [menu-bar sh-script] (cons "Sh-Script" menu-map)) (define-key menu-map [sh-learn-buffer-indent] '(menu-item "Learn buffer indentation" sh-learn-buffer-indent - :help "Learn how to indent the buffer the way it currently is.")) + :help "Learn how to indent the buffer the way it currently is.")) (define-key menu-map [sh-learn-line-indent] '(menu-item "Learn line indentation" sh-learn-line-indent - :help "Learn how to indent a line as it currently is indented")) + :help "Learn how to indent a line as it currently is indented")) (define-key menu-map [sh-show-indent] '(menu-item "Show indentation" sh-show-indent :help "Show the how the current line would be indented")) @@ -491,13 +485,9 @@ This is buffer-local in every such buffer.") (define-key menu-map [sh-pair] '(menu-item "Insert braces and quotes in pairs" - (lambda () - (interactive) - (require 'skeleton) - (setq skeleton-pair (not skeleton-pair))) - :button (:toggle . (and (boundp 'skeleton-pair) - skeleton-pair)) - :help "Inserting a brace or quote automatically inserts the matching pair")) + electric-pair-mode + :button (:toggle . (bound-and-true-p electric-pair-mode)) + :help "Inserting a brace or quote automatically inserts the matching pair")) (define-key menu-map [sh-s0] '("--")) ;; Insert @@ -506,7 +496,7 @@ This is buffer-local in every such buffer.") :help "Insert a function definition")) (define-key menu-map [sh-add] '(menu-item "Addition..." sh-add - :help "Insert an addition of VAR and prefix DELTA for Bourne (type) shell")) + :help "Insert an addition of VAR and prefix DELTA for Bourne (type) shell")) (define-key menu-map [sh-until] '(menu-item "Until Loop" sh-until :help "Insert an until loop")) @@ -537,16 +527,16 @@ This is buffer-local in every such buffer.") (define-key menu-map [sh-s1] '("--")) (define-key menu-map [sh-exec] '(menu-item "Execute region" sh-execute-region - :help "Pass optional header and region to a subshell for noninteractive execution")) + :help "Pass optional header and region to a subshell for noninteractive execution")) (define-key menu-map [sh-exec-interpret] '(menu-item "Execute script..." executable-interpret - :help "Run script with user-specified args, and collect output in a buffer")) + :help "Run script with user-specified args, and collect output in a buffer")) (define-key menu-map [sh-set-shell] '(menu-item "Set shell type..." sh-set-shell :help "Set this buffer's shell to SHELL (a string)")) (define-key menu-map [sh-backslash-region] '(menu-item "Backslash region" sh-backslash-region - :help "Insert, align, or delete end-of-line backslashes on the lines in the region.")) + :help "Insert, align, or delete end-of-line backslashes on the lines in the region.")) map) "Keymap used in Shell-Script mode.") @@ -564,9 +554,10 @@ This is buffer-local in every such buffer.") :group 'sh-script) (defcustom sh-assignment-regexp - '((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=") + `((csh . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=") ;; actually spaces are only supported in let/(( ... )) - (ksh88 . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=") + (ksh88 . ,(concat "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?" + "[ \t]*\\(?:[-+*/%&|~^]\\|<<\\|>>\\)?=")) (bash . "\\<\\([[:alnum:]_]+\\)\\(\\[.+\\]\\)?\\+?=") (rc . "\\<\\([[:alnum:]_*]+\\)[ \t]*=") (sh . "\\<\\([[:alnum:]_]+\\)=")) @@ -1379,10 +1370,10 @@ punctuation characters like '-'." (defconst sh-indent-supported - '((sh . t) + '((sh . sh) (csh . nil) - (rc . t)) - "Shell types that shell indenting can do something with.") + (rc . rc)) + "Indentation rule set to use for each shell type.") (defvar sh-indent-supported-here nil "Non-nil if we support indentation for the current buffer's shell type.") @@ -1464,9 +1455,8 @@ buffer indents as it currently is indented. \\[sh-set-shell] Set this buffer's shell, and maybe its magic number. \\[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 '', \"\", ``. +`sh-electric-here-document-mode' controls whether insertion of two +unquoted < insert a here document. If you generally program a shell different from your login shell you can set `sh-shell-file' accordingly. If your shell's file name doesn't correctly @@ -1503,13 +1493,13 @@ with your script for an edit-interpret-debug cycle." #'sh-syntax-propertize-function) (add-hook 'syntax-propertize-extend-region-functions #'syntax-propertize-multiline 'append 'local) + (sh-electric-here-document-mode 1) (set (make-local-variable 'skeleton-pair-alist) '((?` _ ?`))) (set (make-local-variable 'skeleton-pair-filter-function) 'sh-quoted-p) (set (make-local-variable 'skeleton-further-elements) '((< '(- (min sh-indentation (current-column)))))) (set (make-local-variable 'skeleton-filter-function) 'sh-feature) (set (make-local-variable 'skeleton-newline-indent-rigidly) t) - (set (make-local-variable 'sh-indent-supported-here) nil) (set (make-local-variable 'defun-prompt-regexp) (concat "^\\(function[ \t]\\|[[:alnum:]]+[ \t]+()[ \t]+\\)")) ;; Parse or insert magic number for exec, and set all variables depending @@ -1519,23 +1509,15 @@ with your script for an edit-interpret-debug cycle." (goto-char (point-min)) (looking-at "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")) (match-string 2)) - ((not buffer-file-name) - sh-shell-file) + ((not buffer-file-name) sh-shell-file) ;; Checks that use `buffer-file-name' follow. - ((string-match "\\.m?spec\\'" buffer-file-name) - "rpm") - ((string-match "[.]sh\\>" buffer-file-name) - "sh") - ((string-match "[.]bash\\>" buffer-file-name) - "bash") - ((string-match "[.]ksh\\>" buffer-file-name) - "ksh") - ((string-match "[.]csh\\>" buffer-file-name) - "csh") - ((equal (file-name-nondirectory buffer-file-name) ".profile") - "sh") - (t - sh-shell-file)) + ((string-match "\\.m?spec\\'" buffer-file-name) "rpm") + ((string-match "[.]sh\\>" buffer-file-name) "sh") + ((string-match "[.]bash\\>" buffer-file-name) "bash") + ((string-match "[.]ksh\\>" buffer-file-name) "ksh") + ((string-match "[.]csh\\>" buffer-file-name) "csh") + ((equal (file-name-nondirectory buffer-file-name) ".profile") "sh") + (t sh-shell-file)) nil nil)) ;;;###autoload @@ -1578,6 +1560,426 @@ This adds rules for comments and assignments." "Function to get better fontification including keywords and builtins." (sh-font-lock-keywords-1 t)) +;;; Indentation and navigation with SMIE. + +(require 'smie) + +;; The SMIE code should generally be preferred, but it currently does not obey +;; the various indentation custom-vars, and it misses some important features +;; of the old code, mostly: sh-learn-line/buffer-indent, sh-show-indent, +;; sh-name/save/load-style. +(defvar sh-use-smie nil + "Whether to use the SMIE code for navigation and indentation.") + +(defun sh-smie--keyword-p (tok) + "Non-nil if TOK (at which we're looking) really is a keyword." + (let ((prev (funcall smie-backward-token-function))) + (if (zerop (length prev)) + (looking-back "\\s(" (1- (point))) + (assoc prev smie-grammar)))) + +(defun sh-smie--newline-semi-p (&optional tok) + "Return non-nil if a newline should be treated as a semi-colon. +Here we assume that a newline should be treated as a semi-colon unless it +comes right after a special keyword. +This function does not pay attention to line-continuations. +If TOK is nil, point should be before the newline; otherwise, TOK is the token +before the newline and in that case point should be just before the token." + (save-excursion + (unless tok + (setq tok (funcall smie-backward-token-function))) + (if (and (zerop (length tok)) + (looking-back "\\s(" (1- (point)))) + nil + (not (numberp (nth 2 (assoc tok smie-grammar))))))) + +;;;; SMIE support for `sh'. + +(defconst sh-smie-sh-grammar + (smie-prec2->grammar + (smie-bnf->prec2 + '((exp) ;A constant, or a $var, or a sequence of them... + (cmd ("case" exp "in" branches "esac") + ("if" cmd "then" cmd "fi") + ("if" cmd "then" cmd "else" cmd "fi") + ("if" cmd "then" cmd "elif" cmd "then" cmd "fi") + ("if" cmd "then" cmd "elif" cmd "then" cmd "else" cmd "fi") + ("if" cmd "then" cmd "elif" cmd "then" cmd + "elif" cmd "then" cmd "else" cmd "fi") + ("while" cmd "do" cmd "done") + ("until" cmd "do" cmd "done") + ("for" exp "in" cmd "do" cmd "done") + ("for" exp "do" cmd "done") + ("select" exp "in" cmd "do" cmd "done") ;bash&zsh&ksh88. + ("repeat" exp "do" cmd "done") ;zsh. + (exp "always" exp) ;zsh. + (cmd "|" cmd) (cmd "|&" cmd) + (cmd "&&" cmd) (cmd "||" cmd) + (cmd ";" cmd) (cmd "&" cmd)) + (pattern (pattern "|" pattern)) + (branches (branches ";;" branches) + (branches ";&" branches) (branches ";;&" branches) ;bash. + (pattern "case-)" cmd))) + '((assoc ";;" ";&" ";;&")) + '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&"))))) + +(defconst sh-smie--sh-operators + (delq nil (mapcar (lambda (x) + (setq x (car x)) + (and (stringp x) + (not (string-match "\\`[a-z]" x)) + x)) + sh-smie-sh-grammar))) + +(defconst sh-smie--sh-operators-re (regexp-opt sh-smie--sh-operators)) +(defconst sh-smie--sh-operators-back-re + (concat "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*" + "\\(" sh-smie--sh-operators-re "\\)")) + +(defun sh-smie--sh-keyword-in-p () + "Assuming we're looking at \"in\", return non-nil if it's a keyword. +Does not preserve point." + (let ((forward-sexp-function nil) + (words nil) ;We've seen words. + (newline nil) ;We've seen newlines after the words. + (res nil) + prev) + (while (not res) + (setq prev (funcall smie-backward-token-function)) + (cond + ((zerop (length prev)) + (if newline + (progn (assert words) (setq res 'word)) + (setq words t) + (condition-case nil + (forward-sexp -1) + (scan-error (setq res 'unknown))))) + ((equal prev ";") + (if words (setq newline t) + (setq res 'keyword))) + ((member prev '("case" "for" "select")) (setq res 'keyword)) + ((assoc prev smie-grammar) (setq res 'word)) + (t + (if newline + (progn (assert words) (setq res 'word)) + (setq words t))))) + (eq res 'keyword))) + +(defun sh-smie--sh-keyword-p (tok) + "Non-nil if TOK (at which we're looking) really is a keyword." + (if (equal tok "in") + (sh-smie--sh-keyword-in-p) + (sh-smie--keyword-p tok))) + +(defun sh-smie-sh-forward-token () + (if (and (looking-at "[ \t]*\\(?:#\\|\\(\\s|\\)\\|$\\)") + (save-excursion + (skip-chars-backward " \t") + (not (bolp)))) + (if (and (match-end 1) (not (nth 3 (syntax-ppss)))) + ;; Right before a here-doc. + (let ((forward-sexp-function nil)) + (forward-sexp 1) + ;; Pretend the here-document is a "newline representing a + ;; semi-colon", since the here-doc otherwise covers the newline(s). + ";") + (let ((semi (sh-smie--newline-semi-p))) + (forward-line 1) + (if semi ";" + (sh-smie-sh-forward-token)))) + (forward-comment (point-max)) + (cond + ((looking-at "\\\\\n") (forward-line 1) (sh-smie-sh-forward-token)) + ((looking-at sh-smie--sh-operators-re) + (goto-char (match-end 0)) + (let ((tok (match-string-no-properties 0))) + (if (and (memq (aref tok (1- (length tok))) '(?\; ?\& ?\|)) + (looking-at "[ \t]*\\(?:#\\|$\\)")) + (forward-line 1)) + tok)) + (t + (let* ((pos (point)) + (tok (smie-default-forward-token))) + (cond + ((equal tok ")") "case-)") + ((and tok (string-match "\\`[a-z]" tok) + (assoc tok smie-grammar) + (not + (save-excursion + (goto-char pos) + (sh-smie--sh-keyword-p tok)))) + " word ") + (t tok))))))) + +(defun sh-smie--looking-back-at-continuation-p () + (save-excursion + (and (if (eq (char-before) ?\n) (progn (forward-char -1) t) (eolp)) + (looking-back "\\(?:^\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\\\" + (line-beginning-position))))) + +(defun sh-smie-sh-backward-token () + (let ((bol (line-beginning-position)) + pos tok) + (forward-comment (- (point))) + (cond + ((and (bolp) (not (bobp)) + (equal (syntax-after (1- (point))) (string-to-syntax "|")) + (not (nth 3 (syntax-ppss)))) + ;; Right after a here-document. + (let ((forward-sexp-function nil)) + (forward-sexp -1) + ;; Pretend the here-document is a "newline representing a + ;; semi-colon", since the here-doc otherwise covers the newline(s). + ";")) + ((< (point) bol) + (cond + ((sh-smie--looking-back-at-continuation-p) + (forward-char -1) + (funcall smie-backward-token-function)) + ((sh-smie--newline-semi-p) ";") + (t (funcall smie-backward-token-function)))) + ((looking-back sh-smie--sh-operators-back-re + (line-beginning-position) 'greedy) + (goto-char (match-beginning 1)) + (match-string-no-properties 1)) + (t + (let ((tok (smie-default-backward-token))) + (cond + ((equal tok ")") "case-)") + ((and tok (string-match "\\`[a-z]" tok) + (assoc tok smie-grammar) + (not (save-excursion (sh-smie--sh-keyword-p tok)))) + " word ") + (t tok))))))) + +(defcustom sh-indent-after-continuation t + "If non-nil, try to make sure text is indented after a line continuation." + :type 'boolean) + +(defun sh-smie--continuation-start-indent () + "Return the initial indentation of a continued line. +May return nil if the line should not be treated as continued." + (save-excursion + (forward-line -1) + (unless (sh-smie--looking-back-at-continuation-p) + (current-indentation)))) + +(defun sh-smie-sh-rules (kind token) + (pcase (cons kind token) + (`(:elem . basic) sh-indentation) + (`(:after . "case-)") (or sh-indentation smie-indent-basic)) + ((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))) + ;; FIXME: Maybe this handling of ;; should be made into + ;; a smie-rule-terminator function that takes the substitute ";" as arg. + (`(:before . ,(or `";;" `";&" `";;&")) + (if (and (smie-rule-bolp) (looking-at ";;?&?[ \t]*\\(#\\|$\\)")) + (cons 'column (smie-indent-keyword ";")) + (smie-rule-separator kind))) + (`(:after . ,(or `";;" `";&" `";;&")) + (with-demoted-errors + (smie-backward-sexp token) + (cons 'column + (if (or (smie-rule-bolp) + (save-excursion + (and (member (funcall smie-backward-token-function) + '("in" ";;")) + (smie-rule-bolp)))) + (current-column) + (smie-indent-calculate))))) + (`(:after . "|") (if (smie-rule-parent-p "|") nil 4)) + )) + +;; (defconst sh-smie-csh-grammar +;; (smie-prec2->grammar +;; (smie-bnf->prec2 +;; '((exp) ;A constant, or a $var, or a sequence of them… +;; (elseifcmd (cmd) +;; (cmd "else" "else-if" exp "then" elseifcmd)) +;; (cmd ("switch" branches "endsw") +;; ("if" exp) +;; ("if" exp "then" cmd "endif") +;; ("if" exp "then" cmd "else" cmd "endif") +;; ("if" exp "then" elseifcmd "endif") +;; ;; ("if" exp "then" cmd "else" cmd "endif") +;; ;; ("if" exp "then" cmd "else" "if" exp "then" cmd "endif") +;; ;; ("if" exp "then" cmd "else" "if" exp "then" cmd +;; ;; "else" cmd "endif") +;; ;; ("if" exp "then" cmd "else" "if" exp "then" cmd +;; ;; "else" "if" exp "then" cmd "endif") +;; ("while" cmd "end") +;; ("foreach" cmd "end") +;; (cmd "|" cmd) (cmd "|&" cmd) +;; (cmd "&&" cmd) (cmd "||" cmd) +;; (cmd ";" cmd) (cmd "&" cmd)) +;; ;; This is a lie, but (combined with the corresponding disambiguation +;; ;; rule) it makes it more clear that `case' and `default' are the key +;; ;; separators and the `:' is a secondary tokens. +;; (branches (branches "case" branches) +;; (branches "default" branches) +;; (exp ":" branches))) +;; '((assoc "else" "then" "endif")) +;; '((assoc "case" "default") (nonassoc ":")) +;; '((assoc ";;" ";&" ";;&")) +;; '((assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&"))))) + +;;;; SMIE support for `rc'. + +(defconst sh-smie-rc-grammar + (smie-prec2->grammar + (smie-bnf->prec2 + '((exp) ;A constant, or a $var, or a sequence of them... + (cmd (cmd "case" cmd) + ("if" exp) + ("switch" exp) + ("for" exp) ("while" exp) + (cmd "|" cmd) (cmd "|&" cmd) + (cmd "&&" cmd) (cmd "||" cmd) + (cmd ";" cmd) (cmd "&" cmd)) + (pattern (pattern "|" pattern)) + (branches (branches ";;" branches) + (branches ";&" branches) (branches ";;&" branches) ;bash. + (pattern "case-)" cmd))) + '((assoc ";;" ";&" ";;&")) + '((assoc "case") (assoc ";" "&") (assoc "&&" "||") (assoc "|" "|&"))))) + +(defun sh-smie--rc-after-special-arg-p () + "Check if we're after the first arg of an if/while/for/... construct. +Returns the construct's token and moves point before it, if so." + (forward-comment (- (point))) + (when (looking-back ")\\|\\_