1;;; sh-script.el --- shell-script editing commands for Emacs 2;; Copyright (C) 1993 Free Software Foundation, Inc. 3 4;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr> 5;; Maintainer: FSF 6;; Keywords: shell programming 7 8;; This file is part of GNU Emacs. 9 10;; GNU Emacs is free software; you can redistribute it and/or modify 11;; it under the terms of the GNU General Public License as published by 12;; the Free Software Foundation; either version 2, or (at your option) 13;; any later version. 14 15;; GNU Emacs is distributed in the hope that it will be useful, 16;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18;; GNU General Public License for more details. 19 20;; You should have received a copy of the GNU General Public License 21;; along with GNU Emacs; see the file COPYING. If not, write to 22;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 23 24;;; Commentary: 25 26;; Major mode for editing shell scripts. Currently sh, ksh, bash and csh, 27;; tcsh are supported. Structured statements can be inserted with one 28;; command. 29 30;;; Code: 31 32;; page 1: variables and settings 33;; page 2: mode-command and utility functions 34;; page 3: statement syntax-commands for various shells 35;; page 4: various other commands 36 37 38;;;###autoload 39(setq auto-mode-alist 40 ;; matches files 41 ;; - who's path contains /bin/, but not directories 42 (cons '("/bin/" . sh-or-other-mode) 43 ;; - that have a suffix .sh or .shar (shell archive) 44 ;; - that contain ressources for the various shells 45 ;; - startup files for X11 46 (cons '("\\.sh$\\|\\.shar$\\|/\\.\\(profile\\|bash_profile\\|login\\|bash_login\\|logout\\|bash_logout\\|bashrc\\|t?cshrc\\|xinitrc\\|startxrc\\|xsession\\)$" . sh-mode) 47 auto-mode-alist))) 48 49 50(defvar sh-mode-syntax-table 51 (let ((table (copy-syntax-table))) 52 (modify-syntax-entry ?\# "<" table) 53 (modify-syntax-entry ?\^l ">#" table) 54 (modify-syntax-entry ?\n ">#" table) 55 (modify-syntax-entry ?\" "\"\"" table) 56 (modify-syntax-entry ?\' "\"'" table) 57 (modify-syntax-entry ?\` "$`" table) 58 (modify-syntax-entry ?$ "_" table) 59 (modify-syntax-entry ?! "_" table) 60 (modify-syntax-entry ?% "_" table) 61 (modify-syntax-entry ?: "_" table) 62 (modify-syntax-entry ?. "_" table) 63 (modify-syntax-entry ?^ "_" table) 64 (modify-syntax-entry ?~ "_" table) 65 table) 66 "Syntax table in use in Shell-Script mode.") 67 68 69 70(defvar sh-use-prefix nil 71 "If non-nil when loading, `$' and `<' will be C-c $ and C-c < .") 72 73(defvar sh-mode-map 74 (let ((map (make-sparse-keymap))) 75 (define-key map "\C-c(" 'sh-function) 76 (define-key map "\C-c\C-w" 'sh-while) 77 (define-key map "\C-c\C-u" 'sh-until) 78 (define-key map "\C-c\C-s" 'sh-select) 79 (define-key map "\C-c\C-l" 'sh-indexed-loop) 80 (define-key map "\C-c\C-i" 'sh-if) 81 (define-key map "\C-c\C-f" 'sh-for) 82 (define-key map "\C-c\C-c" 'sh-case) 83 84 (define-key map (if sh-use-prefix "\C-c$" "$") 85 'sh-query-for-variable) 86 (define-key map "=" 'sh-assignment) 87 (define-key map "\C-c+" 'sh-add) 88 (define-key map (if sh-use-prefix "\C-c<" "<") 89 'sh-maybe-here-document) 90 (define-key map "(" 'pair-insert-maybe) 91 (define-key map "{" 'pair-insert-maybe) 92 (define-key map "[" 'pair-insert-maybe) 93 (define-key map "'" 'pair-insert-maybe) 94 (define-key map "`" 'pair-insert-maybe) 95 (define-key map "\"" 'pair-insert-maybe) 96 97 (define-key map "\t" 'sh-indent-line) 98 (substitute-key-definition 'complete-tag 'comint-dynamic-complete-filename 99 map (current-global-map)) 100 (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent 101 map (current-global-map)) 102 ;; Now that tabs work properly, this might be unwanted. 103 (substitute-key-definition 'delete-backward-char 104 'backward-delete-char-untabify 105 map (current-global-map)) 106 (define-key map "\C-c:" 'sh-set-shell) 107 (substitute-key-definition 'beginning-of-defun 108 'sh-beginning-of-compound-command 109 map (current-global-map)) 110 (substitute-key-definition 'backward-sentence 'sh-beginning-of-command 111 map (current-global-map)) 112 (substitute-key-definition 'forward-sentence 'sh-end-of-command 113 map (current-global-map)) 114 (substitute-key-definition 'manual-entry 'sh-manual-entry 115 map (current-global-map)) 116 (define-key map [menu-bar insert] 117 (cons "Insert" (make-sparse-keymap "Insert"))) 118 (define-key map [menu-bar insert sh-while] 119 '("While loop" . sh-while)) 120 (define-key map [menu-bar insert sh-until] 121 '("Until loop" . sh-until)) 122 (define-key map [menu-bar insert sh-select] 123 '("Select statement" . sh-select)) 124 (define-key map [menu-bar insert sh-indexed-loop] 125 '("Indexed loop" . sh-indexed-loop)) 126 (define-key map [menu-bar insert sh-if] 127 '("If statement" . sh-if)) 128 (define-key map [menu-bar insert sh-for] 129 '("For loop" . sh-for)) 130 (define-key map [menu-bar insert sh-case] 131 '("Case statement" . sh-case)) 132 map) 133 "Keymap used in Shell-Script mode.") 134 135 136 137(defvar sh-find-file-modifies t 138 "*What to do when newly found file has no magic number: 139 nil do nothing 140 t insert magic number 141 other insert magic number, but mark as unmodified.") 142 143 144(defvar sh-query-for-magic t 145 "*If non-nil, ask user before changing or inserting magic number.") 146 147 148(defvar sh-magicless-file-regexp "/\\.[^/]+$" 149 "*On files with this kind of name no magic is inserted or changed.") 150 151 152;; someone who understands /etc/magic better than me should beef this up 153;; this currently covers only SCO Unix and Sinix executables 154;; the elegant way would be to read /etc/magic 155(defvar magic-number-alist '(("L\^a\^h\\|\^?ELF" . hexl-mode) 156 ("#!.*perl" . perl-mode)) 157 "A regexp to match the magic number of a found file. 158Currently this is only used by function `sh-or-other-mode'.") 159 160 161(defvar sh-executable ".* is \\([^ \t]*\\)\n" 162 "*Regexp to match the output of sh builtin `type' command on your machine. 163The regexp must match the whole output, and must contain a \\(something\\) 164construct which matches the actual executable.") 165 166 167 168(defvar sh-chmod-argument "755" 169 "*After saving, if the file is not executable, set this mode. 170The mode can be absolute \"511\" or relative \"u+x\". Do nothing if this is nil.") 171 172 173(defvar sh-shell-path (or (getenv "SHELL") "/bin/sh") 174 "*The executable of the shell being programmed.") 175 176(defvar sh-shell-argument nil 177 "*A single argument for the magic number, or nil.") 178 179(defvar sh-shell nil 180 "The shell being programmed. This is set by \\[sh-set-shell].") 181 182(defvar sh-shell-is-csh nil 183 "The shell being programmed. This is set by \\[sh-set-shell].") 184 185(defvar sh-tab-width 4 186 "The default value for `tab-width' in Shell-Script mode. 187This is the width of tab stops after the indentation of the preceeding line.") 188 189(defvar sh-remember-variable-min 3 190 "*Don't remember variables less than this length for completing reads.") 191 192 193(defvar sh-beginning-of-command 194 "\\([;({`|&]\\|^\\)[ \t]*\\([/~:a-zA-Z0-9]\\)" 195 "*Regexp to determine the beginning of a shell command. 196The actual command starts at the beginning of the second \\(grouping\\).") 197 198(defvar sh-end-of-command 199 "\\([/~:a-zA-Z0-9]\\)[ \t]*\\([;#)}`|&]\\|$\\)" 200 "*Regexp to determine the end of a shell command. 201The actual command ends at the end of the first \\(grouping\\).") 202 203 204 205(defvar sh-assignment-space '(csh tcsh) 206 "List of shells that allow spaces around the assignment =.") 207 208(defvar sh-here-document-word "+" 209 "Word to delimit here documents.") 210 211 212;process-environment 213(defvar sh-variables 214 '(("addsuffix" tcsh) ("allow_null_glob_expansion" bash) 215 ("ampm" tcsh) ("argv" csh tcsh) 216 ("autocorrect" tcsh) ("autoexpand" tcsh) 217 ("autolist" tcsh) ("autologout" tcsh) 218 ("auto_resume" bash) ("BASH" bash) 219 ("BASH_VERSION" bash) ("cdable_vars" bash) 220 ("cdpath" csh tcsh) ("CDPATH" sh ksh bash) 221 ("chase_symlinks" tcsh) ("child" csh tcsh) 222 ("COLUMNS" ksh tcsh) ("correct" tcsh) 223 ("dextract" tcsh) ("echo" csh tcsh) 224 ("edit" tcsh) ("EDITOR") 225 ("el" tcsh) ("ENV" ksh bash) 226 ("ERRNO" ksh) ("EUID" bash) 227 ("FCEDIT" ksh bash) ("FIGNORE" bash) 228 ("fignore" tcsh) ("FPATH" ksh) 229 ("gid" tcsh) ("glob_dot_filenames" bash) 230 ("histchars" bash csh tcsh) ("HISTFILE" ksh bash) 231 ("HISTFILESIZE" bash) ("histlit" tcsh) 232 ("history" csh tcsh) ("history_control" bash) 233 ("HISTSIZE" bash) ("home" csh tcsh) 234 ("HOME") ("HOST" tcsh) 235 ("hostname_completion_file" bash) ("HOSTTYPE" bash tcsh) 236 ("HPATH" tcsh) ("HUSHLOGIN") 237 ("IFS" sh ksh bash) ("ignoreeof" bash csh tcsh) 238 ("IGNOREEOF" bash) ("ignore_symlinks" tcsh) 239 ("LANG") ("LC_COLLATE") 240 ("LC_CTYPE") ("LC_MESSAGES") 241 ("LC_MONETARY") ("LC_NUMERIC") 242 ("LC_TIME") ("LINENO" ksh bash) 243 ("LINES" ksh tcsh) ("listjobs" tcsh) 244 ("listlinks" tcsh) ("listmax" tcsh) 245 ("LOGNAME") ("mail" csh tcsh) 246 ("MAIL") ("MAILCHECK") 247 ("MAILPATH") ("MAIL_WARNING" bash) 248 ("matchbeep" tcsh) ("nobeep" tcsh) 249 ("noclobber" bash csh tcsh) ("noglob" csh tcsh) 250 ("nolinks" bash) ("nonomatch" csh tcsh) 251 ("NOREBIND" tcsh) ("notify" bash) 252 ("no_exit_on_failed_exec" bash) ("NO_PROMPT_VARS" bash) 253 ("oid" tcsh) ("OLDPWD" ksh bash) 254 ("OPTARG" sh ksh bash) ("OPTERR" bash) 255 ("OPTIND" sh ksh bash) ("PAGER") 256 ("path" csh tcsh) ("PATH") 257 ("PPID" ksh bash) ("printexitvalue" tcsh) 258 ("prompt" csh tcsh) ("prompt2" tcsh) 259 ("prompt3" tcsh) ("PROMPT_COMMAND" bash) 260 ("PS1" sh ksh bash) ("PS2" sh ksh bash) 261 ("PS3" ksh) ("PS4" ksh bash) 262 ("pushdsilent" tcsh) ("pushdtohome" tcsh) 263 ("pushd_silent" bash) ("PWD" ksh bash) 264 ("RANDOM" ksh bash) ("recexact" tcsh) 265 ("recognize_only_executables" tcsh) ("REPLY" ksh bash) 266 ("rmstar" tcsh) ("savehist" tcsh) 267 ("SECONDS" ksh bash) ("shell" csh tcsh) 268 ("SHELL") ("SHLVL" bash tcsh) 269 ("showdots" tcsh) ("sl" tcsh) 270 ("status" csh tcsh) ("SYSTYPE" tcsh) 271 ("tcsh" tcsh) ("term" tcsh) 272 ("TERM") ("TERMCAP") 273 ("time" csh tcsh) ("TMOUT" ksh bash) 274 ("tperiod" tcsh) ("tty" tcsh) 275 ("UID" bash) ("uid" tcsh) 276 ("verbose" csh tcsh) ("version" tcsh) 277 ("visiblebell" tcsh) ("VISUAL") 278 ("watch" tcsh) ("who" tcsh) 279 ("wordchars" tcsh)) 280 "Alist of all environment and shell variables used for completing read. 281Variables only understood by some shells are associated to a list of those.") 282 283 284 285(defvar sh-font-lock-keywords 286 '(("[ \t]\\(#.*\\)" 1 font-lock-comment-face) 287 ("\"[^`]*\"\\|'.*'\\|\\\\[^\nntc]" . font-lock-string-face)) 288 "*Rules for highlighting shell scripts. 289This variable is included into the various variables 290`sh-SHELL-font-lock-keywords'. If no such variable exists for some shell, 291this one is used.") 292 293 294(defvar sh-sh-font-lock-keywords 295 (append sh-font-lock-keywords 296 '(("\\(^\\|[^-._a-z0-9]\\)\\(case\\|do\\|done\\|elif\\|else\\|esac\\|fi\\|for\\|if\\|in\\|then\\|until\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t))) 297 "*Rules for highlighting Bourne shell scripts.") 298 299(defvar sh-ksh-font-lock-keywords 300 (append sh-sh-font-lock-keywords 301 '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\|select\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t))) 302 "*Rules for highlighting Korn shell scripts.") 303 304(defvar sh-bash-font-lock-keywords 305 (append sh-sh-font-lock-keywords 306 '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t))) 307 "*Rules for highlighting Bourne again shell scripts.") 308 309 310(defvar sh-csh-font-lock-keywords 311 (append sh-font-lock-keywords 312 '(("\\(^\\|[^-._a-z0-9]\\)\\(breaksw\\|case\\|default\\|else\\|end\\|endif\\|foreach\\|if\\|switch\\|then\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t))) 313 "*Rules for highlighting C shell scripts.") 314 315(defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords 316 "*Rules for highlighting Toronto C shell scripts.") 317 318 319 320;; mode-command and utility functions 321 322;;;###autoload 323(defun sh-or-other-mode () 324 "Decide whether this is a compiled executable or a script. 325Usually the file-names of scripts and binaries cannot be automatically 326distinguished, so the presence of an executable's magic number is used." 327 (funcall (or (let ((l magic-number-alist)) 328 (while (and l 329 (not (looking-at (car (car l))))) 330 (setq l (cdr l))) 331 (cdr (car l))) 332 'sh-mode))) 333 334 335;;;###autoload 336(defun sh-mode () 337 "Major mode for editing shell scripts. 338This mode works for many shells, since they all have roughly the same syntax, 339as far as commands, arguments, variables, pipes, comments etc. are concerned. 340Unless the file's magic number indicates the shell, your usual shell is 341assumed. Since filenames rarely give a clue, they are not further analyzed. 342 343The syntax of the statements varies with the shell being used. The syntax of 344statements can be modified by putting a property on the command or new ones 345defined with `define-sh-skeleton'. For example 346 347 (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\")) 348or 349 (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\")) 350 351where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'. 352 353The following commands are available, based on the current shell's syntax: 354 355\\[sh-case] case statement 356\\[sh-for] for loop 357\\[sh-function] function definition 358\\[sh-if] if statement 359\\[sh-indexed-loop] indexed loop from 1 to n 360\\[sh-select] select statement 361\\[sh-until] until loop 362\\[sh-while] while loop 363 364\\[backward-delete-char-untabify] Delete backward one position, even if it was a tab. 365\\[sh-newline-and-indent] Delete unquoted space and indent new line same as this one. 366\\[sh-end-of-command] Go to end of successive commands. 367\\[sh-beginning-of-command] Go to beginning of successive commands. 368\\[sh-set-shell] Set this buffer's shell, and maybe its magic number. 369\\[sh-manual-entry] Display the Unix manual entry for the current command or shell. 370 371\\[sh-query-for-variable] Unless quoted with \\, query for a variable with completions offered. 372\\[sh-maybe-here-document] Without prefix, following an unquoted < inserts here document. 373{, (, [, ', \", ` 374 Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``." 375 (interactive) 376 (kill-all-local-variables) 377 (set-syntax-table sh-mode-syntax-table) 378 (use-local-map sh-mode-map) 379 (make-local-variable 'indent-line-function) 380 (make-local-variable 'comment-start) 381 (make-local-variable 'comment-start-skip) 382 (make-local-variable 'after-save-hook) 383 (make-local-variable 'require-final-newline) 384 (make-local-variable 'sh-shell-path) 385 (make-local-variable 'sh-shell) 386 (make-local-variable 'sh-shell-is-csh) 387 (make-local-variable 'pair-alist) 388 (make-local-variable 'pair-filter) 389 (make-local-variable 'font-lock-keywords) 390 (make-local-variable 'font-lock-keywords-case-fold-search) 391 (make-local-variable 'sh-variables) 392 (setq major-mode 'sh-mode 393 mode-name "Shell-script" 394 ;; Why can't Emacs have one standard function with some parameters? 395 ;; Only few modes actually analyse the previous line's contents 396 indent-line-function 'sh-indent-line 397 comment-start "# " 398 after-save-hook 'sh-chmod 399 tab-width sh-tab-width 400 ;; C shells do 401 require-final-newline t 402 font-lock-keywords-case-fold-search nil 403 pair-alist '((?` _ ?`)) 404 pair-filter 'sh-quoted-p) 405 ; parse or insert magic number for exec() 406 (goto-char (point-min)) 407 (sh-set-shell 408 (if (looking-at "#![\t ]*\\([^\t\n ]+\\)") 409 (buffer-substring (match-beginning 1) (match-end 1)) 410 sh-shell-path)) 411 ;; find-file is set by `normal-mode' when called by `after-find-file' 412 (and (boundp 'find-file) find-file 413 (or (eq sh-find-file-modifies t) 414 (set-buffer-modified-p nil))) 415 (run-hooks 'sh-mode-hook)) 416;;;###autoload 417(defalias 'shell-script-mode 'sh-mode) 418 419 420 421(defmacro define-sh-skeleton (command documentation &rest definitions) 422 "Define COMMAND with [DOCSTRING] to insert statements as in DEFINITION ... 423Prior definitions (e.g. from ~/.emacs) are maintained. 424Each definition is built up as (SHELL PROMPT ELEMENT ...). Alternately 425a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL). 426 427For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'. 428Each DEFINITION is actually stored as 429 (put COMMAND SHELL (PROMPT ELEMENT ...)), 430which you can also do yourself." 431 (or (stringp documentation) 432 (setq definitions (cons documentation definitions) 433 documentation "")) 434 ;; The compiled version doesn't. 435 (require 'backquote) 436 (`(progn 437 (let ((definitions '(, definitions))) 438 (while definitions 439 ;; skeleton need not be loaded to define these 440 (or (and (not (if (boundp 'skeleton-debug) skeleton-debug)) 441 (get '(, command) (car (car definitions)))) 442 (put '(, command) (car (car definitions)) 443 (if (symbolp (cdr (car definitions))) 444 (get '(, command) (cdr (car definitions))) 445 (cdr (car definitions))))) 446 (setq definitions (cdr definitions)))) 447 (put '(, command) 'menu-enable '(get '(, command) sh-shell)) 448 (defun (, command) () 449 (, documentation) 450 (interactive) 451 (skeleton-insert 452 (or (get '(, command) sh-shell) 453 (error "%s statement syntax not defined for shell %s." 454 '(, command) sh-shell))))))) 455 456 457 458(defun sh-indent-line () 459 "Indent as far as preceding line, then by steps of `tab-width'. 460If previous line starts with a comment, it's considered empty." 461 (interactive) 462 (let ((previous (save-excursion 463 (line-move -1) 464 (back-to-indentation) 465 (if (looking-at comment-start-skip) 466 0 467 (current-column))))) 468 (save-excursion 469 (indent-to (if (eq this-command 'newline-and-indent) 470 previous 471 (if (< (current-column) 472 (progn (back-to-indentation) 473 (current-column))) 474 (if (eolp) previous 0) 475 (if (eolp) 476 (max previous (* (1+ (/ (current-column) tab-width)) 477 tab-width)) 478 (* (1+ (/ (current-column) tab-width)) tab-width)))))) 479 (if (< (current-column) (current-indentation)) 480 (skip-chars-forward " \t")))) 481 482 483(defun sh-remember-variable (var) 484 "Make VARIABLE available for future completing reads in this buffer." 485 (or (< (length var) sh-remember-variable-min) 486 (assoc var sh-variables) 487 (setq sh-variables (cons (list var) sh-variables))) 488 var) 489 490 491;; Augment the standard variables by those found in the environment. 492(if (boundp 'process-environment)(let ((l process-environment)) 493 (while l 494 (sh-remember-variable (substring (car l) 495 0 (string-match "=" (car l)))) 496 (setq l (cdr l))))) 497 498 499 500(defun sh-quoted-p () 501 "Is point preceded by an odd number of backslashes?" 502 (eq 1 (% (- (point) (save-excursion 503 (skip-chars-backward "\\\\") 504 (point))) 505 2))) 506 507 508 509(defun sh-executable (command) 510 "If COMMAND is an executable in $PATH its full name is returned. Else nil." 511 (let ((point (point)) 512 (buffer-modified-p (buffer-modified-p)) 513 buffer-read-only after-change-function) 514 (call-process "sh" nil t nil "-c" (concat "type " command)) 515 (setq point (prog1 (point) 516 (goto-char point))) 517 (prog1 518 (and (looking-at sh-executable) 519 (eq point (match-end 0)) 520 (buffer-substring (match-beginning 1) (match-end 1))) 521 (delete-region (point) point) 522 (set-buffer-modified-p buffer-modified-p)))) 523 524 525 526(defun sh-chmod () 527 "This gets called after saving a file to assure that it be executable. 528You can set the absolute or relative mode with `sh-chmod-argument'." 529 (if sh-chmod-argument 530 (or (file-executable-p buffer-file-name) 531 (shell-command (concat "chmod " sh-chmod-argument 532 " " buffer-file-name))))) 533 534;; statement syntax-commands for various shells 535 536;; You are welcome to add the syntax or even completely new statements as 537;; appropriate for your favorite shell. 538 539(define-sh-skeleton sh-case 540 "Insert a case/switch statement in the current shell's syntax." 541 (sh "expression: " 542 "case " str " in" \n 543 > (read-string "pattern: ") ?\) \n 544 > _ \n 545 ";;" \n 546 ( "other pattern, %s: " 547 < str ?\) \n 548 > \n 549 ";;" \n) 550 < "*)" \n 551 > \n 552 resume: 553 < < "esac") 554 (ksh . sh) 555 (bash . sh) 556 (csh "expression: " 557 "switch( " str " )" \n 558 > "case " (read-string "pattern: ") ?: \n 559 > _ \n 560 "breaksw" \n 561 ( "other pattern, %s: " 562 < "case " str ?: \n 563 > \n 564 "breaksw" \n) 565 < "default:" \n 566 > \n 567 resume: 568 < < "endsw") 569 (tcsh . csh)) 570 571 572 573(define-sh-skeleton sh-for 574 "Insert a for loop in the current shell's syntax." 575 (sh "Index variable: " 576 "for " str " in " _ "; do" \n 577 > ?$ (sh-remember-variable str) \n 578 < "done") 579 (ksh . sh) 580 (bash . sh) 581 (csh "Index variable: " 582 "foreach " str " ( " _ " )" \n 583 > ?$ (sh-remember-variable str) \n 584 < "end") 585 (tcsh . csh)) 586 587 588 589(define-sh-skeleton sh-indexed-loop 590 "Insert an indexed loop from 1 to n in the current shell's syntax." 591 (sh "Index variable: " 592 str "=1" \n 593 "while [ $" str " -le " 594 (read-string "upper limit: ") 595 " ]; do" \n 596 > _ ?$ str \n 597 str ?= (sh-add (sh-remember-variable str) 1) \n 598 < "done") 599 (ksh . sh) 600 (bash . sh) 601 (csh "Index variable: " 602 "@ " str " = 1" \n 603 "while( $" str " <= " 604 (read-string "upper limit: ") 605 " )" \n 606 > _ ?$ (sh-remember-variable str) \n 607 "@ " str "++" \n 608 < "end") 609 (tcsh . csh)) 610 611 612 613(defun sh-add (var delta) 614 "Insert an addition of VAR and prefix DELTA for Bourne type shells." 615 (interactive 616 (list (sh-remember-variable 617 (completing-read "Variable: " sh-variables 618 (lambda (element) 619 (or (not (cdr element)) 620 (memq sh-shell (cdr element)))))) 621 (prefix-numeric-value current-prefix-arg))) 622 (setq delta (concat (if (< delta 0) " - " " + ") 623 (abs delta))) 624 (skeleton-insert 625 (assq sh-shell 626 '((sh "`expr $" var delta "`") 627 (ksh "$(( $" var delta " ))") 628 (bash "$[ $" var delta " ]"))) 629 t)) 630 631 632 633(define-sh-skeleton sh-function 634 "Insert a function definition in the current shell's syntax." 635 (sh () 636 "() {" \n 637 > _ \n 638 < "}") 639 (ksh "name: " 640 "function " str " {" \n 641 > _ \n 642 < "}") 643 (bash "name: " 644 "function " str "() {" \n 645 > _ \n 646 < "}")) 647 648 649 650(define-sh-skeleton sh-if 651 "Insert an if statement in the current shell's syntax." 652 (sh "condition: " 653 "if [ " str " ]; then" \n 654 > _ \n 655 ( "other condition, %s: " 656 < "elif [ " str " ]; then" \n 657 > \n) 658 < "else" \n 659 > \n 660 resume: 661 < "fi") 662 (ksh . sh) 663 (bash . sh) 664 (csh "condition: " 665 "if( " str " ) then" \n 666 > _ \n 667 ( "other condition, %s: " 668 < "else if ( " str " ) then" \n 669 > \n) 670 < "else" \n 671 > \n 672 resume: 673 < "endif") 674 (tcsh . csh)) 675 676 677 678(define-sh-skeleton sh-select 679 "Insert a select statement in the current shell's syntax." 680 (ksh "Index variable: " 681 "select " str " in " _ "; do" \n 682 > ?$ str \n 683 < "done")) 684(put 'sh-select 'menu-enable '(get 'sh-select sh-shell)) 685 686 687 688(define-sh-skeleton sh-until 689 "Insert an until loop in the current shell's syntax." 690 (sh "condition: " 691 "until [ " str " ]; do" \n 692 > _ \n 693 < "done") 694 (ksh . sh) 695 (bash . sh)) 696(put 'sh-until 'menu-enable '(get 'sh-until sh-shell)) 697 698 699(define-sh-skeleton sh-while 700 "Insert a while loop in the current shell's syntax." 701 (sh "condition: " 702 "while [ " str " ]; do" \n 703 > _ \n 704 < "done") 705 (ksh . sh) 706 (bash . sh) 707 (csh "condition: " 708 "while( " str " )" \n 709 > _ \n 710 < "end") 711 (tcsh . csh)) 712 713 714 715(defun sh-query-for-variable (arg) 716 "Unless quoted with `\\', query for variable-name with completions. 717Prefix arg 0 means don't insert `$' before the variable. 718Prefix arg 2 or more means only do self-insert that many times. 719 If { is pressed as the first character, it will surround the variable name." 720 (interactive "*p") 721 (or (prog1 (or (> arg 1) 722 (sh-quoted-p)) 723 (self-insert-command arg)) 724 (let (completion-ignore-case 725 (minibuffer-local-completion-map 726 (or (get 'sh-query-for-variable 'keymap) 727 (put 'sh-query-for-variable 'keymap 728 (copy-keymap minibuffer-local-completion-map)))) 729 (buffer (current-buffer))) 730 ;; local function that depends on `arg' and `buffer' 731 (define-key minibuffer-local-completion-map "{" 732 (lambda () (interactive) 733 (if (or arg (> (point) 1)) 734 (beep) 735 (save-window-excursion 736 (setq arg t) 737 (switch-to-buffer-other-window buffer) 738 (insert "{}"))))) 739 (insert 740 (prog1 741 (sh-remember-variable 742 (completing-read "Variable: " sh-variables 743 (lambda (element) 744 (or (not (cdr element)) 745 (memq sh-shell (cdr element)))))) 746 (if (eq t arg) (forward-char 1)))) 747 (if (eq t arg) (forward-char 1))))) 748 749 750 751(defun sh-assignment (arg) 752 "Insert self. Remember previous identifier for future completing read." 753 (interactive "p") 754 (if (eq arg 1) 755 (sh-remember-variable 756 (save-excursion 757 (buffer-substring 758 (progn 759 (if (memq sh-shell sh-assignment-space) 760 (skip-chars-backward " \t")) 761 (point)) 762 (progn 763 (skip-chars-backward "a-zA-Z0-9_") 764 (point)))))) 765 (self-insert-command arg)) 766 767 768 769(defun sh-maybe-here-document (arg) 770 "Inserts self. Without prefix, following unquoted `<' inserts here document. 771The document is bounded by `sh-here-document-word'." 772 (interactive "*P") 773 (self-insert-command (prefix-numeric-value arg)) 774 (or arg 775 (not (eq (char-after (- (point) 2)) last-command-char)) 776 (save-excursion 777 (goto-char (- (point) 2)) 778 (sh-quoted-p)) 779 (progn 780 (insert sh-here-document-word) 781 (or (looking-at "[ \t\n]") (insert ? )) 782 (end-of-line 1) 783 (newline) 784 (save-excursion (insert ?\n sh-here-document-word))))) 785 786 787;; various other commands 788 789(autoload 'comint-dynamic-complete-filename "comint" 790 "Dynamically complete the filename at point." t) 791 792 793 794(defun sh-newline-and-indent (&optional arg) 795 "Strip unquoted whitespace, insert newline, and indent like current line. 796Unquoted whitespace is stripped from the current line's end, unless a 797prefix ARG is given." 798 (interactive "*P") 799 (let ((previous (current-indentation)) 800 (end-of-line (point))) 801 (if arg () 802 (skip-chars-backward " \t") 803 (and (< (point) end-of-line) 804 (sh-quoted-p) 805 (forward-char 1)) 806 (delete-region (point) end-of-line)) 807 (newline) 808 (indent-to previous))) 809 810 811 812(defun sh-set-shell (shell) 813 "Set this buffer's shell to SHELL (a string). 814Calls the value of `sh-set-shell-hook' if set." 815 (interactive "sName or path of shell: ") 816 (save-excursion 817 (goto-char (point-min)) 818 (setq sh-shell-path (if (file-name-absolute-p shell) 819 shell 820 (or (sh-executable shell) 821 (error "Cannot find %s." shell))) 822 sh-shell (intern (file-name-nondirectory sh-shell-path)) 823 sh-shell-is-csh (memq sh-shell '(csh tcsh)) 824 font-lock-keywords 825 (intern-soft (format "sh-%s-font-lock-keywords" sh-shell)) 826 font-lock-keywords (if (and font-lock-keywords 827 (boundp font-lock-keywords)) 828 (symbol-value font-lock-keywords) 829 sh-font-lock-keywords) 830 comment-start-skip (if sh-shell-is-csh 831 "\\(^\\|[^$]\\|\\$[^{]\\)#+[\t ]*" 832 "\\(^\\|[^$]\\|\\$[^{]\\)\\B#+[\t ]*") 833 mode-line-process (format ": %s" sh-shell) 834 shell (concat sh-shell-path 835 (and sh-shell-argument " ") 836 sh-shell-argument)) 837 (and (not buffer-read-only) 838 (not (if buffer-file-name 839 (string-match sh-magicless-file-regexp buffer-file-name))) 840 ;; find-file is set by `normal-mode' when called by `after-find-file' 841 (if (and (boundp 'find-file) find-file) sh-find-file-modifies t) 842 (if (looking-at "#!") 843 (and (skip-chars-forward "#! \t") 844 (not (string= shell 845 (buffer-substring (point) 846 (save-excursion (end-of-line) 847 (point))))) 848 (if sh-query-for-magic 849 (y-or-n-p (concat "Replace magic number by ``#! " 850 shell "''? ")) 851 (message "Magic number ``%s'' replaced." 852 (buffer-substring (point-min) (point)))) 853 (not (delete-region (point) (progn (end-of-line) (point)))) 854 (insert shell)) 855 (insert "#! " shell ?\n)))) 856 (run-hooks 'sh-set-shell-hook)) 857 858 859 860(defun sh-beginning-of-command () 861 "Move point to successive beginnings of commands." 862 (interactive) 863 (if (re-search-backward sh-beginning-of-command nil t) 864 (goto-char (match-beginning 2)))) 865 866 867 868(defun sh-end-of-command () 869 "Move point to successive ends of commands." 870 (interactive) 871 (if (re-search-forward sh-end-of-command nil t) 872 (goto-char (match-end 1)))) 873 874 875 876(defun sh-manual-entry (arg) 877 "Display the Unix manual entry for the current command or shell. 878Universal argument ARG, is passed to `Man-getpage-in-background'." 879 (interactive "P") 880 (let ((command (save-excursion 881 (sh-beginning-of-command) 882 (sh-executable 883 (buffer-substring (point) 884 (progn (forward-sexp) (point))))))) 885 (setq command (read-input (concat "Manual entry (default " 886 (symbol-name sh-shell) 887 "): ") 888 (if command 889 (file-name-nondirectory command)))) 890 (manual-entry (if (string= command "") 891 (symbol-name sh-shell) 892 command) 893 arg))) 894 895;; sh-script.el ends here 896