Retire old subvers file
[dragonfly.git] / contrib / tcsh / csh-mode.el
1 ;; csh-mode.el --- csh (and tcsh) script editing mode for Emacs.
2 ;;
3 ;; Version:    1.2
4 ;; Date:       April 2, 1999
5 ;; Maintainer: Dan Harkless <dan@wave.eng.uci.edu>
6 ;;
7 ;; Description:
8 ;;   csh and tcsh script editing mode for Emacs.
9 ;; 
10 ;; Installation:
11 ;;   Put csh-mode.el in some directory in your load-path and load it.
12 ;;
13 ;; Usage:
14 ;;   This major mode assists shell script writers with indentation
15 ;;   control and control structure construct matching in much the same
16 ;;   fashion as other programming language modes. Invoke describe-mode
17 ;;   for more information.
18 ;; 
19 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20 ;;
21 ;; Author key:
22 ;;   DH - Dan Harkless     <dan@wave.eng.uci.edu>
23 ;;   CM - Carlo Migliorini <migliorini@sodalia.it>
24 ;;   JR - Jack Repenning   <jackr@sgi.com>
25 ;;   GE - Gary Ellison     <Gary.F.Ellison@att.com>
26 ;;
27 ;; *** REVISION HISTORY ***
28 ;;
29 ;; DATE MOD.  BY  REASON FOR MODIFICATION
30 ;; ---------  --  --------------------------------------------------------------
31 ;;  2 Apr 99  DH  1.2: Noticed an out-of-date comment referencing .bashrc etc.
32 ;; 11 Dec 96  DH  1.1: ksh-mode just indented continuation lines by 1 space.
33 ;;                csh-mode looks at the first line and indents properly to line
34 ;;                up under the open-paren, quote, or command.  
35 ;; 11 Dec 96  DH  Added fontification for history substitutions.
36 ;; 10 Dec 96  DH  Added indentation and fontification for labels.  Added
37 ;;                fontification for variables and backquoted strings.
38 ;;  9 Dec 96  DH  1.0: Brought csh-mode up to the level of functionality of
39 ;;                the original ksh-mode.
40 ;;  7 Oct 96  CM  0.1: Hacked ksh-mode.el into minimally functional csh-mode.el
41 ;;                by doing search-and-replace and some keyword changes.
42 ;;  8 Aug 96  JR  (Last modification to ksh-mode 2.6.)
43 ;;                [...]
44 ;; 19 Jun 92  GE  (Conception of ksh-mode.)
45 ;;
46 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
47
48
49 (defconst csh-mode-version "1.2"
50   "*Version number of this version of csh-mode")
51
52 (defvar csh-mode-hook 
53   '(lambda ()
54      (auto-fill-mode 1))
55   "Hook to run each time csh-mode is entered.")
56
57
58 ;;
59 ;; -------------------------------------------> Variables controlling completion
60 ;;
61 (defvar csh-completion-list '())
62 (make-variable-buffer-local 'csh-completion-list)
63 (set-default 'csh-completion-list  '())
64 ;;
65 ;; -type-  : type number, 0:misc, 1:variable, 2:function
66 ;; -regexp-: regexp used to parse the script
67 ;; -match- : used by match-beginning/end to pickup target
68 ;;
69 (defvar csh-completion-type-misc 0)
70 (defvar csh-completion-regexp-var "\\([A-Za-z_0-9]+\\)=")
71 (defvar csh-completion-type-var 1)
72 (defvar csh-completion-match-var 1) 
73 (defvar csh-completion-regexp-var2 "\\$\\({\\|{#\\)?\\([A-Za-z_0-9]+\\)[#%:}]?")
74 (defvar csh-completion-match-var2 2)
75 (defvar csh-completion-regexp-function
76   "\\(function\\)?[ \t]*\\([A-Za-z_0-9]+\\)[ \t]*([ \t]*)")
77 (defvar csh-completion-type-function 2)
78 (defvar csh-completion-match-function 2)
79
80
81 ;;
82 ;; ------------------------------------> Variables controlling indentation style
83 ;;
84 (defvar csh-indent 4
85   "*Indentation of csh statements with respect to containing block. A value
86 of nil indicates compound list keyword \(\"do\" and \"then\"\) alignment.")
87
88 (defvar csh-case-item-offset csh-indent
89   "*Additional indentation for case items within a case statement.")
90 (defvar csh-case-indent nil
91   "*Additional indentation for statements under case items.")
92 (defvar csh-comment-regexp "^\\s *#"
93   "*Regular expression used to recognize comments. Customize to support
94 csh-like languages.")
95 (defvar csh-match-and-tell t
96   "*If non-nil echo in the minibuffer the matching compound command
97 for the \"breaksw\", \"end\", or \"endif\".")
98 (defvar csh-tab-always-indent t
99   "*Controls the operation of the TAB key. If t (the default), always
100 reindent the current line.  If nil, indent the current line only if
101 point is at the left margin or in the line's indentation; otherwise
102 insert a tab.")
103
104
105 ;;
106 ;; ----------------------------------------> Constants containing syntax regexps
107 ;; 
108 (defconst csh-case-default-re
109   "^\\s *\\(case\\|default\\)\\b"
110   "Regexp used to locate grouping keywords case and default" )
111
112 (defconst csh-case-item-re "^\\s *\\(case .*\\|default\\):"
113   "Regexp used to match case-items")
114
115 (defconst csh-end-re "^\\s *end\\b"
116   "Regexp used to match keyword: end")
117
118 (defconst csh-endif-re "^\\s *endif\\b"
119   "Regexp used to match keyword: endif")
120
121 (defconst csh-endsw-re "^\\s *endsw\\b"
122   "Regexp used to match keyword: endsw")
123
124 (defconst csh-else-re "^\\s *\\belse\\(\\b\\|$\\)"
125   "Regexp used to match keyword: else")
126
127 (defconst csh-else-if-re "^\\s *\\belse if\\(\\b\\|$\\)"
128   "Regexp used to match keyword pair: else if")
129
130 (defconst csh-if-re "^\\s *if\\b.+\\(\\\\\\|\\bthen\\b\\)"
131   "Regexp used to match non-one-line if statements")
132
133 (defconst csh-iteration-keywords-re "^[^#\n]*\\s\"*\\b\\(while\\|foreach\\)\\b"
134   "Match one of the keywords: while, foreach")
135
136 (defconst csh-keywords-re
137   "^\\s *\\(else\\b\\|foreach\\b\\|if\\b.+\\(\\\\\\|\\bthen\\b\\)\\|switch\\b\\|while\\b\\)"
138   "Regexp used to detect compound command keywords: else, if, foreach, while")
139
140 (defconst csh-label-re "^\\s *[^!#$\n ]+:"
141   "Regexp used to match flow-control labels")
142
143 (defconst csh-multiline-re "^.*\\\\$"
144   "Regexp used to match a line with a statement using more lines.")
145
146 (defconst csh-switch-re "^\\s *switch\\b"
147   "Regexp used to match keyword: switch")
148
149
150 ;;
151 ;; ----------------------------------------> Variables controlling fontification
152 ;;
153 (defvar csh-keywords '("@" "alias" "bg" "break" "breaksw" "case" "cd" "chdir" 
154                        "continue" "default" "dirs" "echo" "else" "end" "endif"
155                        "endsw" "eval" "exec" "exit" "fg" "foreach" "glob" "goto"
156                        "hashstat" "history" "if" "jobs" "kill" "limit" "login"
157                        "logout" "limit" "notify" "onintr" "popd" "printenv"
158                        "pushd" "rehash" "repeat" "set" "setenv" "shift" "source"
159                        "stop" "suspend" "switch" "then" "time" "umask" "unalias"
160                        "unhash" "unlimit" "unset" "unsetenv" "wait" "while"
161                        ;; tcsh-keywords
162                        "alloc" "bindkey" "builtins" "complete" "echotc"
163                        "filetest" "hup" "log" "ls-F" "nice" "nohup" "sched"
164                        "settc" "setty" "telltc" "uncomplete" "where" "which"))
165
166 (require 'font-lock)  ; need to do this before referring to font-lock-* below
167
168 (defconst csh-font-lock-keywords
169   ;; NOTE:  The order of some of the items in this list is significant.  Do not
170   ;;        alphabetize or otherwise blindly rearrange.
171   (list
172    ;; Comments on line 1, which are missed by syntactic fontification.
173    '("^#.*" 0 font-lock-comment-face)
174
175    ;; Label definitions (1 means first parenthesized exp in regexp).
176    '("^\\s *\\([^!#$\n ]+\\):" 1 font-lock-function-name-face)
177   
178    ;; Label references.
179    '("\\b\\(goto\\|onintr\\)\\b\\s +\\([^!#$ \n\t]+\\)"
180      2 font-lock-function-name-face)
181   
182    ;; Variable settings.
183    '("\\(@\\|set\\|setenv\\)\\s +\\([0-9A-Za-z_]+\\b\\)"
184      2 font-lock-variable-name-face)
185    
186    ;; Variable references not inside of strings.
187    '("\\$[][0-9A-Za-z_#:?]+" 0 font-lock-variable-name-face)
188
189    ;; Backquoted strings.  'keep' means to just fontify non-fontified text.
190    '("`\\(.*\\)`" 1 font-lock-reference-face keep)
191
192    ;; NOTE:  The following variables need to be anchored to the beginning of
193    ;;        line to prevent re-fontifying text in comments.  Due to this, we
194    ;;        can only catch a finite number of occurrences.  More can be added.
195    ;;        The 't' means to override previous fontification.
196    ;;
197    ;;        Variable references inside of " strings.
198    '("^[^#\n]*\".*\\(\\$[][0-9A-Za-z_#:?]+\\).*\""
199      1 font-lock-variable-name-face t)                                    ; 1
200    '("^[^#\n]*\".*\\(\\$[][0-9A-Za-z_#:?]+\\).*\\$[][0-9A-Za-z_#:?]+.*\""
201      1 font-lock-variable-name-face t)                                    ; 2
202    (cons (concat "^[^#\n]*\".*\\(\\$[][0-9A-Za-z_#:?]+\\).*"
203                  "\\$[][0-9A-Za-z_#:?]+.*\\$[][0-9A-Za-z_#:?]+.*\"")
204          (list 1 font-lock-variable-name-face t))                         ; 3
205    ;;
206    ;;        History substitutions.  
207    '("^![^~= \n\t]+" 0 font-lock-reference-face t)                      ; BOL
208    '("^[^#\n]*[^#\\\n]\\(![^~= \n\t]+\\)" 1 font-lock-reference-face t) ; 1
209    '("^[^#\n]*[^#\\\n]\\(![^~= \n\t]+\\).*![^~= \n\t]+"
210      1 font-lock-reference-face t)                                      ; 2
211
212    ;; Keywords.
213    (cons (concat
214           "\\(\\<"
215           (mapconcat 'identity csh-keywords "\\>\\|\\<")
216           "\\>\\)")
217          1)
218    ))
219
220 (put 'csh-mode 'font-lock-keywords 'csh-font-lock-keywords)
221
222
223 ;;
224 ;; -------------------------------------------------------> Mode-specific tables
225 ;;
226 (defvar csh-mode-abbrev-table nil
227   "Abbrev table used while in csh mode.")
228 (define-abbrev-table 'csh-mode-abbrev-table ())
229
230 (defvar csh-mode-map nil 
231   "Keymap used in csh mode")
232 (if csh-mode-map
233     ()
234   (setq csh-mode-map (make-sparse-keymap))
235 ;;(define-key csh-mode-map "\177"    'backward-delete-char-untabify)
236   (define-key csh-mode-map "\C-c\t"  'csh-completion-init-and-pickup)
237   (define-key csh-mode-map "\C-j"    'reindent-then-newline-and-indent)
238   (define-key csh-mode-map "\e\t"    'csh-complete-symbol)
239   (define-key csh-mode-map "\n"      'reindent-then-newline-and-indent)
240   (define-key csh-mode-map '[return] 'reindent-then-newline-and-indent)
241   (define-key csh-mode-map "\t"      'csh-indent-command)
242 ;;(define-key csh-mode-map "\t"      'csh-indent-line)
243   )
244
245 (defvar csh-mode-syntax-table nil
246   "Syntax table used while in csh mode.")
247 (if csh-mode-syntax-table
248     ;; If it's already set up, don't change it.
249     ()
250   ;; Else, create it from the standard table and modify entries that need to be.
251   (setq csh-mode-syntax-table (make-syntax-table))
252   (modify-syntax-entry ?&  "."  csh-mode-syntax-table) ; & -punctuation
253   (modify-syntax-entry ?*  "."  csh-mode-syntax-table) ; * -punctuation
254   (modify-syntax-entry ?-  "."  csh-mode-syntax-table) ; - -punctuation
255   (modify-syntax-entry ?=  "."  csh-mode-syntax-table) ; = -punctuation
256   (modify-syntax-entry ?+  "."  csh-mode-syntax-table) ; + -punctuation
257   (modify-syntax-entry ?|  "."  csh-mode-syntax-table) ; | -punctuation
258   (modify-syntax-entry ?<  "."  csh-mode-syntax-table) ; < -punctuation
259   (modify-syntax-entry ?>  "."  csh-mode-syntax-table) ; > -punctuation
260   (modify-syntax-entry ?/  "."  csh-mode-syntax-table) ; / -punctuation
261   (modify-syntax-entry ?\' "\"" csh-mode-syntax-table) ; ' -string quote
262   (modify-syntax-entry ?.  "w"  csh-mode-syntax-table) ; . -word constituent
263   (modify-syntax-entry ??  "w"  csh-mode-syntax-table) ; ? -word constituent
264
265   ;; \n - comment ender, first character of 2-char comment sequence
266   (modify-syntax-entry ?\n "> 1" csh-mode-syntax-table) ; # -word constituent
267
268   ;;   - whitespace, first character of 2-char comment sequence
269   (modify-syntax-entry ?   "  1" csh-mode-syntax-table) ; 
270
271   ;; \t - whitespace, first character of 2-char comment sequence
272   (modify-syntax-entry ?\t "  1" csh-mode-syntax-table) ; # -word constituent
273
274   ;; # - word constituent, second character of 2-char comment sequence
275   (modify-syntax-entry ?#  "w 2" csh-mode-syntax-table) ; # -word constituent
276   )
277
278
279 ;;
280 ;; ------------------------------------------------------------------> Functions
281 ;;
282 (defun csh-current-line ()
283   "Return the vertical position of point in the buffer.
284 Top line is 1."
285   (+ (count-lines (point-min) (point))
286      (if (= (current-column) 0) 1 0))
287   )
288
289 (defun csh-get-compound-level 
290   (begin-re end-re anchor-point &optional balance-list)
291   "Determine how much to indent this structure. Return a list (level line) 
292 of the matching compound command or nil if no match found."
293   (let* 
294       (;; Locate the next compound begin keyword bounded by point-min
295        (match-point (if (re-search-backward begin-re (point-min) t)
296                         (match-beginning 0) 0))
297        (nest-column (if (zerop match-point)
298                         1 
299                       (progn
300                         (goto-char match-point)
301                         (current-indentation))))
302        (nest-list (cons 0 0))    ;; sentinel cons since cdr is >= 1
303        )
304     (if (zerop match-point)
305         nil ;; graceful exit from recursion
306       (progn
307         (if (nlistp balance-list)
308             (setq balance-list (list)))
309         ;; Now search forward from matching start keyword for end keyword
310         (while (and (consp nest-list) (zerop (cdr nest-list))
311                     (re-search-forward end-re anchor-point t))
312           (if (not (memq (point) balance-list))
313               (progn
314                 (setq balance-list (cons (point) balance-list))
315                 (goto-char match-point)  ;; beginning of compound cmd
316                 (setq nest-list
317                       (csh-get-compound-level begin-re end-re
318                                              anchor-point balance-list))
319                 )))
320
321         (cond ((consp nest-list)
322                (if (zerop (cdr nest-list))
323                  (progn
324                    (goto-char match-point)
325                    (cons nest-column (csh-current-line)))
326                  nest-list))
327               (t nil)
328               )
329         )
330       )
331     )
332   )
333
334 (defun csh-get-nest-level ()
335   "Return a 2 element list (nest-level nest-line) describing where the
336 current line should nest."
337   (let ((case-fold-search)
338         (level))
339     (save-excursion
340       (forward-line -1)
341       (while (and (not (bobp))
342                   (null level))
343         (if (and (not (looking-at "^\\s *$"))
344                  (not (save-excursion
345                         (forward-line -1)
346                         (beginning-of-line)
347                         (looking-at csh-multiline-re)))
348                  (not (looking-at csh-comment-regexp)))
349             (setq level (cons (current-indentation)
350                               (csh-current-line)))
351           (forward-line -1)
352           );; if
353         );; while
354       (if (null level)
355           (cons (current-indentation) (csh-current-line))
356         level)
357       )
358     )
359   )
360
361 (defun csh-get-nester-column (nest-line)
362   "Return the column to indent to with respect to nest-line taking 
363 into consideration keywords and other nesting constructs."
364   (save-excursion 
365     (let ((fence-post)
366           (case-fold-search)
367           (start-line (csh-current-line)))
368       ;;
369       ;; Handle case item indentation constructs for this line
370       (cond ((looking-at csh-case-item-re)
371              ;; This line is a case item...
372              (save-excursion
373                (goto-line nest-line)
374                (let ((fence-post (save-excursion (end-of-line) (point))))
375                  (cond ((re-search-forward csh-switch-re fence-post t)
376                         ;; If this is the first case under the switch, indent.
377                         (goto-char (match-beginning 0))
378                         (+ (current-indentation) csh-case-item-offset))
379
380                        ((re-search-forward csh-case-item-re fence-post t)
381                         ;; If this is another case right under a previous case
382                         ;; without intervening code, stay at the same
383                         ;; indentation. 
384                         (goto-char (match-beginning 0))
385                         (current-indentation))
386                        
387                        (t
388                         ;; Else, this is a new case.  Outdent.
389                         (- (current-indentation) csh-case-item-offset))
390                        )
391                  )))
392             (t;; Not a case-item.  What to do relative to the nest-line?
393              (save-excursion
394                (goto-line nest-line)
395                (setq fence-post (save-excursion (end-of-line) (point)))
396                (save-excursion
397                  (cond
398                   ;;
399                   ;; Check if we are in a continued statement
400                   ((and (looking-at csh-multiline-re)
401                         (save-excursion
402                           (goto-line (1- start-line))
403                           (looking-at csh-multiline-re)))
404                    (if (looking-at ".*[\'\"]\\\\")
405                        ;; If this is a continued string, indent under
406                        ;; opening quote.
407                        (progn
408                          (re-search-forward "[\'\"]")
409                          (forward-char -1))
410                      (if (looking-at ".*([^\)\n]*\\\\")
411                          ;; Else if this is a continued parenthesized
412                          ;; list, indent after paren.
413                          (re-search-forward "(" fence-post t)
414                        ;; Else, indent after whitespace after first word.
415                        (re-search-forward "[^ \t]+[ \t]+" fence-post t)))
416                    (current-column))
417                   
418                   ;; In order to locate the column of the keyword,
419                   ;; which might be embedded within a case-item,
420                   ;; it is necessary to use re-search-forward.
421                   ;; Search by literal case, since shell is
422                   ;; case-sensitive.
423                   ((re-search-forward csh-keywords-re fence-post t)
424                    (goto-char (match-beginning 1))
425                    (if (looking-at csh-switch-re)
426                        (+ (current-indentation) csh-case-item-offset)
427                      (+ (current-indentation)
428                         (if (null csh-indent)
429                             2 csh-indent)
430                         )))
431                   
432                   ((re-search-forward csh-case-default-re fence-post t)  
433                    (if (null csh-indent)
434                        (progn 
435                          (goto-char (match-end 1))
436                          (+ (current-indentation) 1))
437                      (progn
438                        (goto-char (match-beginning 1))
439                        (+ (current-indentation) csh-indent))
440                      ))
441                   
442                   ;;
443                   ;; Now detect first statement under a case item
444                   ((looking-at csh-case-item-re)
445                    (if (null csh-case-indent)
446                        (progn
447                          (re-search-forward csh-case-item-re fence-post t)
448                          (goto-char (match-end 1))
449                          (+ (current-column) 1))
450                      (+ (current-indentation) csh-case-indent)))
451                   
452                   ;;
453                   ;; If this is the first statement under a control-flow
454                   ;; label, indent one level. 
455                   ((csh-looking-at-label)
456                    (+ (current-indentation) csh-indent))
457                   
458                   ;; This is hosed when using current-column
459                   ;; and there is a multi-command expression as the
460                   ;; nester.
461                   (t (current-indentation)))
462                  )
463                ));; excursion over
464             );; Not a case-item
465       );;let
466     );; excursion
467   );; defun
468
469 (defun csh-indent-command ()
470   "Indent current line relative to containing block and allow for
471 csh-tab-always-indent customization"
472   (interactive)
473   (let (case-fold-search)
474     (cond ((save-excursion
475              (skip-chars-backward " \t")
476              (bolp))
477            (csh-indent-line))
478           (csh-tab-always-indent
479            (save-excursion
480              (csh-indent-line)))
481           (t (insert-tab))
482           ))
483   )
484
485 (defun csh-indent-line ()
486   "Indent current line as far as it should go according
487 to the syntax/context"
488   (interactive)
489   (let (case-fold-search)
490     (save-excursion
491       (beginning-of-line)
492       (if (bobp)
493           nil
494         ;;
495         ;; Align this line to current nesting level
496         (let*
497             (
498              (level-list (csh-get-nest-level)) ; Where to nest against
499              ;;           (last-line-level (car level-list))
500              (this-line-level (current-indentation))
501              (nester-column (csh-get-nester-column (cdr level-list)))
502              (struct-match (csh-match-structure-and-reindent))
503              )
504           (if struct-match
505               (setq nester-column struct-match))
506           (if (eq nester-column this-line-level)
507               nil
508             (beginning-of-line)
509             (let ((beg (point)))
510               (back-to-indentation)
511               (delete-region beg (point)))
512             (indent-to nester-column))
513           );; let*
514         );; if
515       );; excursion
516     ;;
517     ;; Position point on this line
518     (let*
519         (
520          (this-line-level (current-indentation))
521          (this-bol (save-excursion
522                      (beginning-of-line)
523                      (point)))
524          (this-point (- (point) this-bol))
525          )
526       (cond ((> this-line-level this-point);; point in initial white space
527              (back-to-indentation))
528             (t nil)
529             );; cond
530       );; let*
531     );; let
532   );; defun
533
534 (defun csh-indent-region (start end)
535   "From start to end, indent each line."
536   ;; The algorithm is just moving through the region line by line with
537   ;; the match noise turned off.  Only modifies nonempty lines.
538   (save-excursion
539     (let (csh-match-and-tell
540           (endmark (copy-marker end)))
541       
542       (goto-char start)
543       (beginning-of-line)
544       (setq start (point))
545       (while (> (marker-position endmark) start)
546         (if (not (and (bolp) (eolp)))
547             (csh-indent-line))
548         (forward-line 1)
549         (setq start (point)))
550
551       (set-marker endmark nil)
552       )
553     )
554   )
555
556 (defun csh-line-to-string ()
557   "From point, construct a string from all characters on
558 current line"
559   (skip-chars-forward " \t") ;; skip tabs as well as spaces
560   (buffer-substring (point)
561                     (progn
562                       (end-of-line 1)
563                       (point))))
564
565 (defun csh-looking-at-label ()
566   "Return true if current line is a label (not the default: case label)."
567   (and
568    (looking-at csh-label-re)
569    (not (looking-at "^\\s *default:"))))
570
571 (defun csh-match-indent-level (begin-re end-re)
572   "Match the compound command and indent. Return nil on no match,
573 indentation to use for this line otherwise."
574   (interactive)
575   (let* ((case-fold-search)
576          (nest-list 
577           (save-excursion
578             (csh-get-compound-level begin-re end-re (point))
579             ))
580          ) ;; bindings
581     (if (null nest-list)
582         (progn
583           (if csh-match-and-tell
584               (message "No matching compound command"))
585           nil) ;; Propagate a miss.
586       (let* (
587              (nest-level (car nest-list))
588              (match-line (cdr nest-list))
589              ) ;; bindings
590         (if csh-match-and-tell
591             (save-excursion
592               (goto-line match-line)
593               (message "Matched ... %s" (csh-line-to-string))
594               ) ;; excursion
595           ) ;; if csh-match-and-tell
596         nest-level ;;Propagate a hit.
597         ) ;; let*
598       ) ;; if
599     ) ;; let*
600   ) ;; defun csh-match-indent-level
601
602 (defun csh-match-structure-and-reindent ()
603   "If the current line matches one of the indenting keywords
604 or one of the control structure ending keywords then reindent. Also
605 if csh-match-and-tell is non-nil the matching structure will echo in
606 the minibuffer"
607   (interactive)
608   (let (case-fold-search)
609     (save-excursion
610       (beginning-of-line)
611       (cond ((looking-at csh-else-re)
612              (csh-match-indent-level csh-if-re csh-endif-re))
613             ((looking-at csh-else-if-re)
614              (csh-match-indent-level csh-if-re csh-endif-re))
615             ((looking-at csh-endif-re)
616              (csh-match-indent-level csh-if-re csh-endif-re))
617             ((looking-at csh-end-re)
618              (csh-match-indent-level csh-iteration-keywords-re csh-end-re))
619             ((looking-at csh-endsw-re)
620              (csh-match-indent-level csh-switch-re csh-endsw-re))
621             ((csh-looking-at-label)
622              ;; Flush control-flow labels left since they don't nest.
623              0)
624             ;;
625             (t nil)
626             );; cond
627       )
628     ))
629
630 ;;;###autoload
631 (defun csh-mode ()
632   "csh-mode 2.0 - Major mode for editing csh and tcsh scripts.
633 Special key bindings and commands:
634 \\{csh-mode-map}
635 Variables controlling indentation style:
636 csh-indent
637     Indentation of csh statements with respect to containing block.
638     Default value is 4.
639 csh-case-indent
640     Additional indentation for statements under case items.
641     Default value is nil which will align the statements one position 
642     past the \")\" of the pattern.
643 csh-case-item-offset
644     Additional indentation for case items within a case statement.
645     Default value is 2.
646 csh-tab-always-indent
647     Controls the operation of the TAB key. If t (the default), always
648     reindent the current line.  If nil, indent the current line only if
649     point is at the left margin or in the line's indentation; otherwise
650     insert a tab.
651 csh-match-and-tell
652     If non-nil echo in the minibuffer the matching compound command
653     for the \"done\", \"}\", \"fi\", or \"endsw\". Default value is t.
654
655 csh-comment-regexp
656   Regular expression used to recognize comments. Customize to support
657   csh-like languages. Default value is \"\^\\\\s *#\".
658
659 Style Guide.
660  By setting
661     (setq csh-indent default-tab-width)
662
663     The following style is obtained:
664
665     if [ -z $foo ]
666             then
667                     bar    # <-- csh-group-offset is additive to csh-indent
668                     foo
669     fi
670
671  By setting
672     (setq csh-indent default-tab-width)
673     (setq csh-group-offset (- 0 csh-indent))
674
675     The following style is obtained:
676
677     if [ -z $foo ]
678     then
679             bar
680             foo
681     fi
682
683  By setting
684     (setq csh-case-item-offset 1)
685     (setq csh-case-indent nil)
686
687     The following style is obtained:
688
689     case x in *
690      foo) bar           # <-- csh-case-item-offset
691           baz;;         # <-- csh-case-indent aligns with \")\"
692      foobar) foo
693              bar;;
694     endsw
695
696  By setting
697     (setq csh-case-item-offset 1)
698     (setq csh-case-indent 6)
699
700     The following style is obtained:
701
702     case x in *
703      foo) bar           # <-- csh-case-item-offset
704            baz;;        # <-- csh-case-indent
705      foobar) foo
706            bar;;
707     endsw
708     
709
710 Installation:
711   Put csh-mode.el in some directory in your load-path.
712   Put the following forms in your .emacs file.
713
714  (setq auto-mode-alist
715       (append auto-mode-alist
716               (list
717                '(\"\\\\.csh$\" . csh-mode)
718                '(\"\\\\.login\" . csh-mode))))
719
720  (setq csh-mode-hook
721       (function (lambda ()
722          (font-lock-mode 1)             ;; font-lock the buffer
723          (setq csh-indent 8)
724          (setq csh-tab-always-indent t)
725          (setq csh-match-and-tell t)
726          (setq csh-align-to-keyword t)  ;; Turn on keyword alignment
727          )))"
728   (interactive)
729   (kill-all-local-variables)
730   (use-local-map csh-mode-map)
731   (setq major-mode 'csh-mode)
732   (setq mode-name "Csh")
733   (setq local-abbrev-table csh-mode-abbrev-table)
734   (set-syntax-table csh-mode-syntax-table)
735   (make-local-variable 'indent-line-function)
736   (setq indent-line-function 'csh-indent-line)
737   (make-local-variable 'indent-region-function)
738   (setq indent-region-function 'csh-indent-region)
739   (make-local-variable 'comment-start)
740   (setq comment-start "# ")
741   (make-local-variable 'comment-end)
742   (setq comment-end "")
743   (make-local-variable 'comment-column)
744   (setq comment-column 32)
745   (make-local-variable 'comment-start-skip)
746   (setq comment-start-skip "#+ *")
747   ;;
748   ;; config font-lock mode
749   (make-local-variable 'font-lock-keywords) 
750   (setq font-lock-keywords csh-font-lock-keywords)
751   ;;
752   ;; Let the user customize
753   (run-hooks 'csh-mode-hook)
754   ) ;; defun
755
756 ;;
757 ;; Completion code supplied by Haavard Rue <hrue@imf.unit.no>.
758 ;;
759 ;;
760 ;; add a completion with a given type to the list
761 ;;
762 (defun csh-addto-alist (completion type)
763   (setq csh-completion-list
764         (append csh-completion-list
765                 (list (cons completion type)))))
766
767 (defun csh-bol-point ()
768   (save-excursion
769     (beginning-of-line)
770     (point)))
771
772 (defun csh-complete-symbol ()
773   "Perform completion."
774   (interactive)
775   (let* ((case-fold-search)
776          (end (point))
777          (beg (unwind-protect
778                   (save-excursion
779                     (backward-sexp 1)
780                     (while (= (char-syntax (following-char)) ?\')
781                       (forward-char 1))
782                     (point))))
783          (pattern (buffer-substring beg end))
784          (predicate 
785           ;;
786           ;; ` or $( mark a function
787           ;;
788           (save-excursion
789             (goto-char beg)
790             (if (or
791                  (save-excursion
792                    (backward-char 1)
793                    (looking-at "`"))
794                  (save-excursion
795                    (backward-char 2)
796                    (looking-at "\\$(")))
797                 (function (lambda (sym)
798                             (equal (cdr sym) csh-completion-type-function)))
799               ;;
800               ;; a $, ${ or ${# mark a variable
801               ;;
802               (if (or
803                    (save-excursion
804                      (backward-char 1)
805                      (looking-at "\\$"))
806                    (save-excursion
807                      (backward-char 2)
808                      (looking-at "\\${"))
809                    (save-excursion
810                      (backward-char 3)
811                      (looking-at "\\${#")))
812                   (function (lambda (sym)
813                               (equal (cdr sym)
814                                      csh-completion-type-var)))
815                 ;;
816                 ;; don't know. use 'em all
817                 ;;
818                 (function (lambda (sym) t))))))
819          ;;
820          (completion (try-completion pattern csh-completion-list predicate)))
821     ;;
822     (cond ((eq completion t))
823           ;;
824           ;; oops, what is this ?
825           ;;
826           ((null completion)
827            (message "Can't find completion for \"%s\"" pattern))
828           ;;
829           ;; insert
830           ;;
831           ((not (string= pattern completion))
832            (delete-region beg end)
833            (insert completion))
834           ;;
835           ;; write possible completion in the minibuffer,
836           ;; use this instead of a seperate buffer (usual)
837           ;;
838           (t
839            (let ((list (all-completions pattern csh-completion-list predicate))
840                  (string ""))
841              (while list
842                (progn
843                  (setq string (concat string (format "%s " (car list))))
844                  (setq list (cdr list))))
845              (message string))))))
846
847 ;;
848 ;; init the list and pickup all 
849 ;;
850 (defun csh-completion-init-and-pickup ()
851   (interactive)
852   (let (case-fold-search)
853     (csh-completion-list-init)
854     (csh-pickup-all)))
855
856 ;;
857 ;; init the list
858 ;;
859 (defun csh-completion-list-init ()
860   (interactive)
861   (setq csh-completion-list
862         (list
863          (cons "break"  csh-completion-type-misc)
864          (cons "breaksw"  csh-completion-type-misc)
865          (cons "case"  csh-completion-type-misc)
866          (cons "continue"  csh-completion-type-misc)
867          (cons "endif"  csh-completion-type-misc)
868          (cons "exit"  csh-completion-type-misc)
869          (cons "foreach"  csh-completion-type-misc)
870          (cons "if"  csh-completion-type-misc)
871          (cons "while"  csh-completion-type-misc))))
872
873 (defun csh-eol-point ()
874   (save-excursion
875     (end-of-line)
876     (point)))
877
878 (defun csh-pickup-all ()
879   "Pickup all completions in buffer."
880   (interactive)
881   (csh-pickup-completion-driver (point-min) (point-max) t))
882
883 (defun csh-pickup-completion (regexp type match pmin pmax)
884   "Pickup completion in region and addit to the list, if not already
885 there." 
886   (let ((i 0) kw obj)
887     (save-excursion
888       (goto-char pmin)
889       (while (and
890               (re-search-forward regexp pmax t)
891               (match-beginning match)
892               (setq kw  (buffer-substring
893                          (match-beginning match)
894                          (match-end match))))
895         (progn
896           (setq obj (assoc kw csh-completion-list))
897           (if (or (equal nil obj)
898                   (and (not (equal nil obj))
899                        (not (= type (cdr obj)))))
900               (progn
901                 (setq i (1+ i))
902                 (csh-addto-alist kw type))))))
903     i))
904
905 (defun csh-pickup-completion-driver (pmin pmax message)
906   "Driver routine for csh-pickup-completion."
907   (if message
908       (message "pickup completion..."))
909   (let* (
910          (i1
911           (csh-pickup-completion  csh-completion-regexp-var
912                                  csh-completion-type-var
913                                  csh-completion-match-var
914                                  pmin pmax))
915          (i2
916           (csh-pickup-completion  csh-completion-regexp-var2
917                                  csh-completion-type-var
918                                  csh-completion-match-var2
919                                  pmin pmax))
920          (i3
921           (csh-pickup-completion  csh-completion-regexp-function
922                                  csh-completion-type-function
923                                  csh-completion-match-function
924                                  pmin pmax)))
925     (if message
926         (message "pickup %d variables and %d functions." (+ i1 i2) i3))))
927
928 (defun csh-pickup-this-line ()
929   "Pickup all completions in current line."
930   (interactive)
931   (csh-pickup-completion-driver (csh-bol-point) (csh-eol-point) nil))
932
933
934 (provide 'csh-mode)
935 ;;; csh-mode.el ends here