xref: /freebsd/contrib/tcsh/csh-mode.el (revision b9f654b163bce26de79705e77b872427c9f2afa1)
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 <software@harkless.org>
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     <software@harkless.org>
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
86of 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
94csh-like languages.")
95(defvar csh-match-and-tell t
96  "*If non-nil echo in the minibuffer the matching compound command
97for 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
100reindent the current line.  If nil, indent the current line only if
101point is at the left margin or in the line's indentation; otherwise
102insert 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.
284Top 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)
292of 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
336current 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
363into 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
471csh-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
487to 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
558current 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,
573indentation 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
604or one of the control structure ending keywords then reindent. Also
605if csh-match-and-tell is non-nil the matching structure will echo in
606the 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.
633Special key bindings and commands:
634\\{csh-mode-map}
635Variables controlling indentation style:
636csh-indent
637    Indentation of csh statements with respect to containing block.
638    Default value is 4.
639csh-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.
643csh-case-item-offset
644    Additional indentation for case items within a case statement.
645    Default value is 2.
646csh-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.
651csh-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
655csh-comment-regexp
656  Regular expression used to recognize comments. Customize to support
657  csh-like languages. Default value is \"\^\\\\s *#\".
658
659Style 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
710Installation:
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
885there."
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
936