diff options
| -rw-r--r-- | lisp/progmodes/sh-script.el | 895 | ||||
| -rw-r--r-- | lisp/skeleton.el | 328 |
2 files changed, 1223 insertions, 0 deletions
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el new file mode 100644 index 00000000000..b0fd04d4a13 --- /dev/null +++ b/lisp/progmodes/sh-script.el | |||
| @@ -0,0 +1,895 @@ | |||
| 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. | ||
| 158 | Currently 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. | ||
| 163 | The regexp must match the whole output, and must contain a \\(something\\) | ||
| 164 | construct 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. | ||
| 170 | The 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. | ||
| 187 | This 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. | ||
| 196 | The 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. | ||
| 201 | The 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. | ||
| 281 | Variables 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. | ||
| 289 | This variable is included into the various variables | ||
| 290 | `sh-SHELL-font-lock-keywords'. If no such variable exists for some shell, | ||
| 291 | this 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. | ||
| 325 | Usually the file-names of scripts and binaries cannot be automatically | ||
| 326 | distinguished, 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. | ||
| 338 | This mode works for many shells, since they all have roughly the same syntax, | ||
| 339 | as far as commands, arguments, variables, pipes, comments etc. are concerned. | ||
| 340 | Unless the file's magic number indicates the shell, your usual shell is | ||
| 341 | assumed. Since filenames rarely give a clue, they are not further analyzed. | ||
| 342 | |||
| 343 | The syntax of the statements varies with the shell being used. The syntax of | ||
| 344 | statements can be modified by putting a property on the command or new ones | ||
| 345 | defined with `define-sh-skeleton'. For example | ||
| 346 | |||
| 347 | (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\")) | ||
| 348 | or | ||
| 349 | (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\")) | ||
| 350 | |||
| 351 | where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'. | ||
| 352 | |||
| 353 | The 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 ... | ||
| 423 | Prior definitions (e.g. from ~/.emacs) are maintained. | ||
| 424 | Each definition is built up as (SHELL PROMPT ELEMENT ...). Alternately | ||
| 425 | a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL). | ||
| 426 | |||
| 427 | For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'. | ||
| 428 | Each DEFINITION is actually stored as | ||
| 429 | (put COMMAND SHELL (PROMPT ELEMENT ...)), | ||
| 430 | which 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'. | ||
| 460 | If 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. | ||
| 528 | You 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. | ||
| 717 | Prefix arg 0 means don't insert `$' before the variable. | ||
| 718 | Prefix 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. | ||
| 771 | The 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. | ||
| 796 | Unquoted whitespace is stripped from the current line's end, unless a | ||
| 797 | prefix 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). | ||
| 814 | Calls 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. | ||
| 878 | Universal 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 | ||
diff --git a/lisp/skeleton.el b/lisp/skeleton.el new file mode 100644 index 00000000000..548644f0fe4 --- /dev/null +++ b/lisp/skeleton.el | |||
| @@ -0,0 +1,328 @@ | |||
| 1 | ;;; skeleton.el --- Metalanguage for writing statement skeletons | ||
| 2 | ;; Copyright (C) 1993 by 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 | ;; A very concise metalanguage for writing structured statement | ||
| 27 | ;; skeleton insertion commands for programming language modes. This | ||
| 28 | ;; originated in shell-script mode and was applied to ada-mode's | ||
| 29 | ;; commands which shrunk to one third. And these commands are now | ||
| 30 | ;; user configurable. | ||
| 31 | |||
| 32 | ;;; Code: | ||
| 33 | |||
| 34 | ;; page 1: statement skeleton metalanguage definition & interpreter | ||
| 35 | ;; page 2: paired insertion | ||
| 36 | ;; page 3: mirror-mode, an example for setting up paired insertion | ||
| 37 | |||
| 38 | |||
| 39 | (defvar skeleton-transformation nil | ||
| 40 | "*If non-nil, function applied to strings before they are inserted. | ||
| 41 | It should take strings and characters and return them transformed, or nil | ||
| 42 | which means no transformation. | ||
| 43 | Typical examples might be `upcase' or `capitalize'.") | ||
| 44 | |||
| 45 | ; this should be a fourth argument to defvar | ||
| 46 | (put 'skeleton-transformation 'variable-interactive | ||
| 47 | "aTransformation function: ") | ||
| 48 | |||
| 49 | |||
| 50 | |||
| 51 | (defvar skeleton-subprompt | ||
| 52 | (substitute-command-keys | ||
| 53 | "RET, \\<minibuffer-local-map>\\[abort-recursive-edit] or \\[help-command]") | ||
| 54 | "*Replacement for %s in prompts of recursive skeleton definitions.") | ||
| 55 | |||
| 56 | |||
| 57 | |||
| 58 | (defvar skeleton-debug nil | ||
| 59 | "*If non-nil `define-skeleton' will override previous definition.") | ||
| 60 | |||
| 61 | |||
| 62 | |||
| 63 | ;;;###autoload | ||
| 64 | (defmacro define-skeleton (command documentation &rest definition) | ||
| 65 | "Define a user-configurable COMMAND that enters a statement skeleton. | ||
| 66 | DOCUMENTATION is that of the command, while the variable of the same name, | ||
| 67 | which contains the definition, has a documentation to that effect. | ||
| 68 | PROMPT and ELEMENT ... are as defined under `skeleton-insert'." | ||
| 69 | (if skeleton-debug | ||
| 70 | (set command definition)) | ||
| 71 | (require 'backquote) | ||
| 72 | (`(progn | ||
| 73 | (defvar (, command) '(, definition) | ||
| 74 | (, (concat "*Definition for the " | ||
| 75 | (symbol-name command) | ||
| 76 | " skeleton command. | ||
| 77 | See function `skeleton-insert' for meaning.")) ) | ||
| 78 | (defun (, command) () | ||
| 79 | (, documentation) | ||
| 80 | (interactive) | ||
| 81 | ;; Don't use last-command to guarantee command does the same thing, | ||
| 82 | ;; whatever other name it is given. | ||
| 83 | (skeleton-insert (, command)))))) | ||
| 84 | |||
| 85 | |||
| 86 | |||
| 87 | ;;;###autoload | ||
| 88 | (defun skeleton-insert (definition &optional no-newline) | ||
| 89 | "Insert the complex statement skeleton DEFINITION describes very concisely. | ||
| 90 | If optional NO-NEWLINE is nil the skeleton will end on a line of its own. | ||
| 91 | |||
| 92 | DEFINITION is made up as (PROMPT ELEMENT ...). PROMPT may be nil if not | ||
| 93 | needed, a prompt-string or an expression for complex read functions. | ||
| 94 | |||
| 95 | If ELEMENT is a string or a character it gets inserted (see also | ||
| 96 | `skeleton-transformation'). Other possibilities are: | ||
| 97 | |||
| 98 | \\n go to next line and align cursor | ||
| 99 | > indent according to major mode | ||
| 100 | < undent tab-width spaces but not beyond beginning of line | ||
| 101 | _ cursor after termination | ||
| 102 | & skip next ELEMENT if previous didn't move point | ||
| 103 | | skip next ELEMENT if previous moved point | ||
| 104 | -num delete num preceding characters | ||
| 105 | resume: skipped, continue here if quit is signaled | ||
| 106 | nil skipped | ||
| 107 | |||
| 108 | ELEMENT may itself be DEFINITION with a PROMPT. The user is prompted | ||
| 109 | repeatedly for different inputs. The DEFINITION is processed as often | ||
| 110 | as the user enters a non-empty string. \\[keyboard-quit] terminates | ||
| 111 | skeleton insertion, but continues after `resume:' and positions at `_' | ||
| 112 | if any. If PROMPT in such a sub-definition contains a \".. %s ..\" it | ||
| 113 | is replaced by `skeleton-subprompt'. | ||
| 114 | |||
| 115 | Other lisp-expressions are evaluated and the value treated as above. | ||
| 116 | The following local variables are available: | ||
| 117 | |||
| 118 | str first time: read a string prompting with PROMPT and insert it | ||
| 119 | if PROMPT is not a string it is evaluated instead | ||
| 120 | then: insert previously read string once more | ||
| 121 | quit non-nil when resume: section is entered by keyboard quit | ||
| 122 | v1, v2 local variables for memorising anything you want" | ||
| 123 | (let (modified opoint point resume: quit v1 v2) | ||
| 124 | (skeleton-internal-list definition (car definition)) | ||
| 125 | (or no-newline | ||
| 126 | (eolp) | ||
| 127 | (newline) | ||
| 128 | (indent-relative t)) | ||
| 129 | (if point | ||
| 130 | (goto-char point)))) | ||
| 131 | |||
| 132 | |||
| 133 | |||
| 134 | (defun skeleton-internal-read (str) | ||
| 135 | (let ((minibuffer-help-form "\ | ||
| 136 | As long as you provide input you will insert another subskeleton. | ||
| 137 | |||
| 138 | If you enter the empty string, the loop inserting subskeletons is | ||
| 139 | left, and the current one is removed as far as it has been entered. | ||
| 140 | |||
| 141 | If you quit, the current subskeleton is removed as far as it has been | ||
| 142 | entered. No more of the skeleton will be inserted, except maybe for a | ||
| 143 | syntactically necessary termination.")) | ||
| 144 | (setq str (if (stringp str) | ||
| 145 | (read-string | ||
| 146 | (format str skeleton-subprompt)) | ||
| 147 | (eval str)))) | ||
| 148 | (if (string= str "") | ||
| 149 | (signal 'quit t) | ||
| 150 | str)) | ||
| 151 | |||
| 152 | |||
| 153 | (defun skeleton-internal-list (definition &optional str recursive start line) | ||
| 154 | (condition-case quit | ||
| 155 | (progn | ||
| 156 | (setq start (save-excursion (beginning-of-line) (point)) | ||
| 157 | column (current-column) | ||
| 158 | line (buffer-substring start | ||
| 159 | (save-excursion (end-of-line) (point))) | ||
| 160 | str (list 'setq 'str | ||
| 161 | (if recursive | ||
| 162 | (list 'skeleton-internal-read (list 'quote str)) | ||
| 163 | (list (if (stringp str) | ||
| 164 | 'read-string | ||
| 165 | 'eval) | ||
| 166 | str)))) | ||
| 167 | (while (setq modified (eq opoint (point)) | ||
| 168 | opoint (point) | ||
| 169 | definition (cdr definition)) | ||
| 170 | (skeleton-internal-1 (car definition))) | ||
| 171 | ;; maybe continue loop | ||
| 172 | recursive) | ||
| 173 | (quit ;; remove the subskeleton as far as it has been shown | ||
| 174 | (if (eq (cdr quit) 'recursive) | ||
| 175 | () | ||
| 176 | ;; the subskeleton shouldn't have deleted outside current line | ||
| 177 | (end-of-line) | ||
| 178 | (delete-region start (point)) | ||
| 179 | (insert line) | ||
| 180 | (move-to-column column)) | ||
| 181 | (if (eq (cdr quit) t) | ||
| 182 | ;; empty string entered | ||
| 183 | nil | ||
| 184 | (while (if definition | ||
| 185 | (not (eq (car (setq definition (cdr definition))) | ||
| 186 | 'resume:)))) | ||
| 187 | (if definition | ||
| 188 | (skeleton-internal-list definition) | ||
| 189 | ;; propagate signal we can't handle | ||
| 190 | (if recursive (signal 'quit 'recursive))))))) | ||
| 191 | |||
| 192 | |||
| 193 | |||
| 194 | (defun skeleton-internal-1 (element) | ||
| 195 | (cond ( (and (integerp element) | ||
| 196 | (< element 0)) | ||
| 197 | (delete-char element)) | ||
| 198 | ( (char-or-string-p element) | ||
| 199 | (insert (if skeleton-transformation | ||
| 200 | (funcall skeleton-transformation element) | ||
| 201 | element)) ) | ||
| 202 | ( (eq element '\n) ; actually (eq '\n 'n) | ||
| 203 | (newline) | ||
| 204 | (indent-relative t) ) | ||
| 205 | ( (eq element '>) | ||
| 206 | (indent-for-tab-command) ) | ||
| 207 | ( (eq element '<) | ||
| 208 | (backward-delete-char-untabify (min tab-width (current-column))) ) | ||
| 209 | ( (eq element '_) | ||
| 210 | (or point | ||
| 211 | (setq point (point))) ) | ||
| 212 | ( (eq element '&) | ||
| 213 | (if modified | ||
| 214 | (setq definition (cdr definition))) ) | ||
| 215 | ( (eq element '|) | ||
| 216 | (or modified | ||
| 217 | (setq definition (cdr definition))) ) | ||
| 218 | ( (if (consp element) | ||
| 219 | (or (stringp (car element)) | ||
| 220 | (consp (car element)))) | ||
| 221 | (while (skeleton-internal-list element (car element) t)) ) | ||
| 222 | ( (null element) ) | ||
| 223 | ( (skeleton-internal-1 (eval element)) ))) | ||
| 224 | |||
| 225 | |||
| 226 | ;; variables and command for automatically inserting pairs like () or "" | ||
| 227 | |||
| 228 | (defvar pair nil | ||
| 229 | "*If this is nil pairing is turned off, no matter what else is set. | ||
| 230 | Otherwise modes with `pair-insert-maybe' on some keys will attempt this.") | ||
| 231 | |||
| 232 | |||
| 233 | (defvar pair-on-word nil | ||
| 234 | "*If this is nil pairing is not attempted before or inside a word.") | ||
| 235 | |||
| 236 | |||
| 237 | (defvar pair-filter (lambda ()) | ||
| 238 | "Attempt pairing if this function returns nil, before inserting. | ||
| 239 | This allows for context-sensitive checking whether pairing is appropriate.") | ||
| 240 | |||
| 241 | |||
| 242 | (defvar pair-alist () | ||
| 243 | "An override alist of pairing partners matched against | ||
| 244 | `last-command-char'. Each alist element, which looks like (ELEMENT | ||
| 245 | ...), is passed to `skeleton-insert' with no prompt. Variable `str' | ||
| 246 | does nothing. | ||
| 247 | |||
| 248 | Elements might be (?` ?` _ \"''\"), (?\\( ? _ \" )\") or (?{ \\n > _ \\n < ?}).") | ||
| 249 | |||
| 250 | |||
| 251 | |||
| 252 | ;;;###autoload | ||
| 253 | (defun pair-insert-maybe (arg) | ||
| 254 | "Insert the character you type ARG times. | ||
| 255 | |||
| 256 | With no ARG, if `pair' is non-nil, and if | ||
| 257 | `pair-on-word' is non-nil or we are not before or inside a | ||
| 258 | word, and if `pair-filter' returns nil, pairing is performed. | ||
| 259 | |||
| 260 | If a match is found in `pair-alist', that is inserted, else | ||
| 261 | the defaults are used. These are (), [], {}, <> and `' for the | ||
| 262 | symmetrical ones, and the same character twice for the others." | ||
| 263 | (interactive "*P") | ||
| 264 | (if (or arg | ||
| 265 | (not pair) | ||
| 266 | (if (not pair-on-word) (looking-at "\\w")) | ||
| 267 | (funcall pair-filter)) | ||
| 268 | (self-insert-command (prefix-numeric-value arg)) | ||
| 269 | (insert last-command-char) | ||
| 270 | (if (setq arg (assq last-command-char pair-alist)) | ||
| 271 | ;; typed char is inserted, and car means no prompt | ||
| 272 | (skeleton-insert arg t) | ||
| 273 | (save-excursion | ||
| 274 | (insert (or (cdr (assq last-command-char | ||
| 275 | '((?( . ?)) | ||
| 276 | (?[ . ?]) | ||
| 277 | (?{ . ?}) | ||
| 278 | (?< . ?>) | ||
| 279 | (?` . ?')))) | ||
| 280 | last-command-char)))))) | ||
| 281 | |||
| 282 | |||
| 283 | |||
| 284 | ;;;###autoload | ||
| 285 | ;; a more serious example can be found in shell-script.el | ||
| 286 | (defun mirror-mode () | ||
| 287 | "This major mode is an amusing little example of paired insertion. | ||
| 288 | All printable characters do a paired self insert, while the other commands | ||
| 289 | work normally." | ||
| 290 | (interactive) | ||
| 291 | (kill-all-local-variables) | ||
| 292 | (make-local-variable 'pair) | ||
| 293 | (make-local-variable 'pair-on-word) | ||
| 294 | (make-local-variable 'pair-filter) | ||
| 295 | (make-local-variable 'pair-alist) | ||
| 296 | (setq major-mode 'mirror-mode | ||
| 297 | mode-name "Mirror" | ||
| 298 | pair-on-word t | ||
| 299 | ;; in the middle column insert one or none if odd window-width | ||
| 300 | pair-filter (lambda () | ||
| 301 | (if (>= (current-column) | ||
| 302 | (/ (window-width) 2)) | ||
| 303 | ;; insert both on next line | ||
| 304 | (next-line 1) | ||
| 305 | ;; insert one or both? | ||
| 306 | (= (* 2 (1+ (current-column))) | ||
| 307 | (window-width)))) | ||
| 308 | ;; mirror these the other way round as well | ||
| 309 | pair-alist '((?) _ ?() | ||
| 310 | (?] _ ?[) | ||
| 311 | (?} _ ?{) | ||
| 312 | (?> _ ?<) | ||
| 313 | (?/ _ ?\\) | ||
| 314 | (?\\ _ ?/) | ||
| 315 | (?` ?` _ "''") | ||
| 316 | (?' ?' _ "``")) | ||
| 317 | ;; in this mode we exceptionally ignore the user, else it's no fun | ||
| 318 | pair t) | ||
| 319 | (let ((map (make-keymap)) | ||
| 320 | (i ? )) | ||
| 321 | (use-local-map map) | ||
| 322 | (setq map (car (cdr map))) | ||
| 323 | (while (< i ?\^?) | ||
| 324 | (aset map i 'pair-insert-maybe) | ||
| 325 | (setq i (1+ i)))) | ||
| 326 | (run-hooks 'mirror-mode-hook)) | ||
| 327 | |||
| 328 | ;; skeleton.el ends here | ||