Initial Commit
[packages] / xemacs-packages / prog-modes / tcl.el
1 ;;; tcl.el --- Tcl code editing commands for Emacs
2
3 ;; Copyright (C) 1994, 1998, 1999, 2000, 2001  Free Software Foundation, Inc.
4
5 ;; Maintainer: FSF
6 ;; Author: Tom Tromey <tromey@busco.lanl.gov>
7 ;;    Chris Lindblad <cjl@lcs.mit.edu>
8 ;; Keywords: languages tcl modes
9 ;; Version: Revision: 1.64.4.1
10
11 ;; This file is part of XEmacs.
12
13 ;; XEmacs is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; any later version.
17
18 ;; XEmacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with XEmacs; see the file COPYING.  If not, write to the
25 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 ;; Boston, MA 02111-1307, USA.
27
28 ;;; Synched up with: Mostly with GNU Emacs 21.2.  The differences are a
29 ;;; couple of defcustom<->defvars, we use `tcl-imenu-generic-expression'
30 ;;; instead of `tcl-imenu-create-index-function' and some typo fixes.
31
32 ;; BEFORE USE:
33 ;;
34 ;; If you plan to use the interface to the TclX help files, you must
35 ;; set the variable tcl-help-directory-list to point to the topmost
36 ;; directories containing the TclX help files.  Eg:
37 ;;
38 ;;   (setq tcl-help-directory-list '("/usr/local/lib/tclx/help"))
39 ;;
40 ;;; Commentary:
41
42 ;; CUSTOMIZATION NOTES:
43 ;; * tcl-proc-list can be used to customize a list of things that
44 ;; "define" other things.  Eg in my project I put "defvar" in this
45 ;; list.
46 ;; * tcl-typeword-list is similar, but uses font-lock-type-face.
47 ;; * tcl-keyword-list is a list of keywords.  I've generally used this
48 ;; for flow-control words.  Eg I add "unwind_protect" to this list.
49 ;; * tcl-type-alist can be used to minimally customize indentation
50 ;; according to context.
51
52 ;; THANKS FOR CRITICISM AND SUGGESTIONS TO:
53 ;; Guido Bosch <Guido.Bosch@loria.fr>
54 ;; pgs1002@esc.cam.ac.uk (Dr P.G. Sjoerdsma)
55 ;; Mike Scheidler <c23mts@kocrsv01.delcoelect.com>
56 ;; Matt Newman <men@charney.colorado.edu>
57 ;; rwhitby@research.canon.oz.au (Rod Whitby)
58 ;; h9118101@hkuxa.hku.hk (Yip Chi Lap [Beta])
59 ;; Pertti Tapio Kasanen <ptk@delta.hut.fi>
60 ;; schmid@fb3-s7.math.TU-Berlin.DE (Gregor Schmid)
61 ;; warsaw@nlm.nih.gov (Barry A. Warsaw)
62 ;; Carl Witty <cwitty@ai.mit.edu>
63 ;; T. V. Raman <raman@crl.dec.com>
64 ;; Jesper Pedersen <blackie@imada.ou.dk>
65 ;; dfarmer@evolving.com (Doug Farmer)
66 ;; "Chris Alfeld" <calfeld@math.utah.edu>
67 ;; Ben Wing <ben@xemacs.org>
68
69 ;; KNOWN BUGS:
70 ;; * In Tcl "#" is not always a comment character.  This can confuse
71 ;;   tcl.el in certain circumstances.  For now the only workaround is
72 ;;   to enclose offending hash characters in quotes or precede it with
73 ;;   a backslash.  Note that using braces won't work -- quotes change
74 ;;   the syntax class of characters between them, while braces do not.
75 ;;   The electric-# mode helps alleviate this problem somewhat.
76 ;; * indent-tcl-exp is untested.
77
78 ;; TODO:
79 ;; * make add-log-tcl-defun smarter.  should notice if we are in the
80 ;;   middle of a defun, or between defuns.  should notice if point is
81 ;;   on first line of defun (or maybe even in comments before defun).
82 ;; * Allow continuation lines to be indented under the first argument
83 ;;   of the preceding line, like this:
84 ;;      [list something \
85 ;;            something-else]
86 ;; * There is a request that indentation work like this:
87 ;;        button .fred -label Fred \
88 ;;                     -command {puts fred}
89 ;; * Should have tcl-complete-symbol that queries the inferior process.
90 ;; * Should have describe-symbol that works by sending the magic
91 ;;   command to a tclX process.
92 ;; * Need C-x C-e binding (tcl-eval-last-exp).
93 ;; * Write indent-region function that is faster than indenting each
94 ;;   line individually.
95 ;; * tcl-figure-type should stop at "beginning of line" (only ws
96 ;;   before point, and no "\" on previous line).  (see tcl-real-command-p).
97 ;; * overrides some comint keybindings; fix.
98 ;; * Trailing \ will eat blank lines.  Should deal with this.
99 ;;   (this would help catch some potential bugs).
100 ;; * Inferior should display in half the screen, not the whole screen.
101 ;; * Indentation should deal with "switch".
102 ;; * Consider writing code to find help files automatically (for
103 ;;   common cases).
104 ;; * `#' shouldn't insert `\#' when point is in string.
105
106 \f
107
108 ;;; Code:
109
110 (eval-when-compile
111   (require 'imenu)
112   (require 'outline)
113   (require 'dabbrev)
114   (require 'add-log))
115
116 ;; I sure wish Emacs had a package that made it easy to extract this
117 ;; sort of information.  Strange definition works with XEmacs 20.0.
118 (defconst tcl-using-emacs-19 (not (string-match "18\\." emacs-version))
119   "Non-nil if using Emacs 19 or later.")
120
121 (defconst tcl-using-emacs-19-23
122   (or (string-match "19\\.\\(2[3-9]\\|[3-9][0-9]\\)" emacs-version)
123       (string-match "^[2-9][0-9]\\." emacs-version))
124   "Non-nil if using Emacs 19-23 or later.")
125
126 (defconst tcl-using-xemacs-19 (string-match "XEmacs" emacs-version)
127   "Non-nil if using XEmacs.")
128
129 (require 'comint)
130
131 ;; When compiling under Emacs, load imenu during compilation.  If
132 ;; you have 19.22 or earlier, comment this out, or get imenu.
133 (and (fboundp 'eval-when-compile)
134      (eval-when-compile
135        (if (and (not (string< emacs-version "19.23"))
136                 (not (string-match "XEmacs" emacs-version)))
137            (require 'imenu))
138        ()))
139
140 (defconst tcl-version "Revision: 1.64.4.1 ")
141 (defconst tcl-maintainer "Tom Tromey <tromey@drip.colorado.edu>")
142
143 ;;
144 ;; User variables.
145 ;;
146
147 (defgroup tcl nil
148   "Major mode for editing Tcl source in Emacs"
149   :group 'languages)
150
151 (defcustom tcl-indent-level 4
152   "*Indentation of Tcl statements with respect to containing block."
153   :group 'tcl
154   :type 'integer)
155
156 (defcustom tcl-continued-indent-level 4
157   "*Indentation of continuation line relative to first line of command."
158   :group 'tcl
159   :type 'integer)
160
161 (defcustom tcl-auto-newline nil
162   "*Non-nil means automatically newline before and after braces you insert."
163   :group 'tcl
164   :type 'boolean)
165
166 (defcustom tcl-tab-always-indent t
167   "*Control effect of TAB key.
168 If t (the default), always indent current line.
169 If nil and point is not in the indentation area at the beginning of
170 the line, a TAB is inserted.
171 Other values cause the first possible action from the following list
172 to take place:
173
174   1. Move from beginning of line to correct indentation.
175   2. Delete an empty comment.
176   3. Move forward to start of comment, indenting if necessary.
177   4. Move forward to end of line, indenting if necessary.
178   5. Create an empty comment.
179   6. Move backward to start of comment, indenting if necessary."
180   :group 'tcl
181   :type '(choice (const :tag "Always" t)
182                  (const :tag "Beginning only" nil)
183                  (const :tag "Maybe move or make or delete comment" 'tcl)))
184
185
186 (defcustom tcl-use-hairy-comment-detector t
187   "*If not nil, use the more sophisticated, but slower, comment-delete method.
188 This variable is not effective in Emacs 18;
189 the fast function is always used in that version."
190   :group 'tcl
191   :type 'boolean)
192
193 (defcustom tcl-electric-hash-style 'smart
194   "*Style of electric hash insertion to use.
195 Possible values are `backslash', meaning that `\\' quoting should be
196 done; `quote', meaning that `\"' quoting should be done; `smart',
197 meaning that the choice between `backslash' and `quote' should be
198 made depending on the number of hashes inserted; or nil, meaning that
199 no quoting should be done.  Any other value for this variable is
200 taken to mean `smart'.  The default is `smart'."
201   :group 'tcl
202   :type '(choice (const backslash) (const quote) (const smart) (const nil)))
203
204 (defcustom tcl-help-directory-list nil
205   "*List of topmost directories containing TclX help files."
206   :group 'tcl
207   :type '(repeat directory))
208
209 (defcustom tcl-use-smart-word-finder t
210   "*If not nil, use smart way to find current word, for Tcl help feature."
211   :group 'tcl
212   :type 'boolean)
213
214 (defcustom tcl-application "wish"
215   "*Name of Tcl program to run in inferior Tcl mode."
216   :group 'tcl
217   :type 'string)
218
219 (defcustom tcl-command-switches nil
220   "*List of switches to supply to the `tcl-application' program."
221   :group 'tcl
222   :type '(repeat string))
223
224 (defcustom tcl-prompt-regexp "^\\(% \\|\\)"
225   "*If not nil, a regexp that will match the prompt in the inferior process.
226 If nil, the prompt is the name of the application with \">\" appended.
227
228 The default is \"^\\(% \\|\\)\", which will match the default primary
229 and secondary prompts for tclsh and wish."
230   :group 'tcl
231   :type 'regexp)
232
233 (defcustom inferior-tcl-source-command "source %s\n"
234   "*Format-string for building a Tcl command to load a file.
235 This format string should use `%s' to substitute a file name
236 and should result in a Tcl expression that will command the
237 inferior Tcl to load that file.  The filename will be appropriately
238 quoted for Tcl."
239   :group 'tcl
240   :type 'string)
241
242 ;;
243 ;; Keymaps, abbrevs, syntax tables.
244 ;;
245
246 (defvar tcl-mode-abbrev-table nil
247   "Abbrev table in use in Tcl-mode buffers.")
248 (if tcl-mode-abbrev-table
249     ()
250   (define-abbrev-table 'tcl-mode-abbrev-table ()))
251
252 (defvar tcl-mode-map ()
253   "Keymap used in Tcl mode.")
254
255 (defvar tcl-mode-syntax-table nil
256   "Syntax table in use in Tcl-mode buffers.")
257 (if tcl-mode-syntax-table
258     ()
259   (setq tcl-mode-syntax-table (make-syntax-table))
260   (modify-syntax-entry ?%  "_" tcl-mode-syntax-table)
261   (modify-syntax-entry ?@  "_" tcl-mode-syntax-table)
262   (modify-syntax-entry ?&  "_" tcl-mode-syntax-table)
263   (modify-syntax-entry ?*  "_" tcl-mode-syntax-table)
264   (modify-syntax-entry ?+  "_" tcl-mode-syntax-table)
265   (modify-syntax-entry ?-  "_" tcl-mode-syntax-table)
266   (modify-syntax-entry ?.  "_" tcl-mode-syntax-table)
267   (modify-syntax-entry ?:  "_" tcl-mode-syntax-table)
268   (modify-syntax-entry ?!  "_" tcl-mode-syntax-table)
269   (modify-syntax-entry ?$  "_" tcl-mode-syntax-table) ; FIXME use "'"?
270   (modify-syntax-entry ?/  "_" tcl-mode-syntax-table)
271   (modify-syntax-entry ?~  "_" tcl-mode-syntax-table)
272   (modify-syntax-entry ?<  "_" tcl-mode-syntax-table)
273   (modify-syntax-entry ?=  "_" tcl-mode-syntax-table)
274   (modify-syntax-entry ?>  "_" tcl-mode-syntax-table)
275   (modify-syntax-entry ?|  "_" tcl-mode-syntax-table)
276   (modify-syntax-entry ?\(  "()" tcl-mode-syntax-table)
277   (modify-syntax-entry ?\)  ")(" tcl-mode-syntax-table)
278   (modify-syntax-entry ?\;  "." tcl-mode-syntax-table)
279   (modify-syntax-entry ?\n ">   " tcl-mode-syntax-table)
280   (modify-syntax-entry ?\f ">   " tcl-mode-syntax-table)
281   (modify-syntax-entry ?# "<   " tcl-mode-syntax-table))
282
283 (defvar inferior-tcl-mode-map nil
284   "Keymap used in Inferior Tcl mode.")
285
286 ;; XEmacs menu.
287 (defvar tcl-xemacs-menu
288   '(["Beginning of function" tcl-beginning-of-defun t]
289     ["End of function" tcl-end-of-defun t]
290     ["Mark function" tcl-mark-defun t]
291     ["Indent region" indent-region (tcl-mark)]
292     ["Comment region" comment-region (tcl-mark)]
293     ["Uncomment region" tcl-uncomment-region (tcl-mark)]
294     "----"
295     ["Show Tcl process buffer" inferior-tcl t]
296     ["Send function to Tcl process" tcl-eval-defun
297      (and inferior-tcl-buffer (get-buffer inferior-tcl-buffer))]
298     ["Send region to Tcl process" tcl-eval-region
299      (and inferior-tcl-buffer (get-buffer inferior-tcl-buffer))]
300     ["Send file to Tcl process" tcl-load-file
301      (and inferior-tcl-buffer (get-buffer inferior-tcl-buffer))]
302     ["Restart Tcl process with file" tcl-restart-with-file t]
303     "----"
304     ["Tcl help" tcl-help-on-word tcl-help-directory-list]
305     ["Send bug report" tcl-submit-bug-report t])
306   "XEmacs menu for Tcl mode.")
307
308 ;; Emacs does menus via keymaps.  Do it in a function in case we
309 ;; later decide to add it to inferior Tcl mode as well.
310 (defun tcl-add-emacs-menu (map)
311   (define-key map [menu-bar] (make-sparse-keymap "Tcl"))
312   ;; This fails in Emacs 19.22 and earlier.
313   (require 'lmenu)
314   (let ((menu (make-lucid-menu-keymap "Tcl" tcl-xemacs-menu)))
315     (define-key map [menu-bar tcl] (cons "Tcl" menu))
316     ;; The following is intended to compute the key sequence
317     ;; information for the menu.  It doesn't work.
318     (x-popup-menu nil menu)))
319
320 (defun tcl-fill-mode-map ()
321   (define-key tcl-mode-map "{" 'tcl-electric-char)
322   (define-key tcl-mode-map "}" 'tcl-electric-brace)
323   (define-key tcl-mode-map "[" 'tcl-electric-char)
324   (define-key tcl-mode-map "]" 'tcl-electric-char)
325   (define-key tcl-mode-map ";" 'tcl-electric-char)
326   (define-key tcl-mode-map "#" 'tcl-electric-hash)
327   ;; FIXME.
328   (define-key tcl-mode-map "\e\C-a" 'tcl-beginning-of-defun)
329   ;; FIXME.
330   (define-key tcl-mode-map "\e\C-e" 'tcl-end-of-defun)
331   ;; FIXME.
332   (define-key tcl-mode-map "\e\C-h" 'tcl-mark-defun)
333   (define-key tcl-mode-map "\e\C-q" 'indent-tcl-exp)
334   (define-key tcl-mode-map "\177" 'backward-delete-char-untabify)
335   (define-key tcl-mode-map "\t" 'tcl-indent-command)
336   (define-key tcl-mode-map "\M-;" 'tcl-indent-for-comment)
337   (define-key tcl-mode-map "\M-\C-x" 'tcl-eval-defun)
338   (define-key tcl-mode-map "\C-c\C-b" 'tcl-submit-bug-report)
339   (and (fboundp 'comment-region)
340        (define-key tcl-mode-map "\C-c\C-c" 'comment-region))
341   (define-key tcl-mode-map "\C-c\C-i" 'tcl-help-on-word)
342   (define-key tcl-mode-map "\C-c\C-v" 'tcl-eval-defun)
343   (define-key tcl-mode-map "\C-c\C-f" 'tcl-load-file)
344   (define-key tcl-mode-map "\C-c\C-t" 'inferior-tcl)
345   (define-key tcl-mode-map "\C-c\C-x" 'tcl-eval-region)
346   (define-key tcl-mode-map "\C-c\C-s" 'switch-to-tcl)
347
348   ;; Make menus.
349   (if (and tcl-using-emacs-19 (not tcl-using-xemacs-19))
350       (progn
351         (tcl-add-emacs-menu tcl-mode-map))))
352
353 (defun tcl-fill-inferior-map ()
354   (define-key inferior-tcl-mode-map "\t" 'comint-dynamic-complete)
355   (define-key inferior-tcl-mode-map "\M-?"
356     'comint-dynamic-list-filename-completions)
357   (define-key inferior-tcl-mode-map "\e\C-a" 'tcl-beginning-of-defun)
358   (define-key inferior-tcl-mode-map "\e\C-e" 'tcl-end-of-defun)
359   (define-key inferior-tcl-mode-map "\177" 'backward-delete-char-untabify)
360   (define-key inferior-tcl-mode-map "\M-\C-x" 'tcl-eval-defun)
361   (define-key inferior-tcl-mode-map "\C-c\C-b" 'tcl-submit-bug-report)
362   (define-key inferior-tcl-mode-map "\C-c\C-i" 'tcl-help-on-word)
363   (define-key inferior-tcl-mode-map "\C-c\C-v" 'tcl-eval-defun)
364   (define-key inferior-tcl-mode-map "\C-c\C-f" 'tcl-load-file)
365   (define-key inferior-tcl-mode-map "\C-c\C-t" 'inferior-tcl)
366   (define-key inferior-tcl-mode-map "\C-c\C-x" 'tcl-eval-region)
367   (define-key inferior-tcl-mode-map "\C-c\C-s" 'switch-to-tcl))
368
369 (if tcl-mode-map
370     ()
371   (setq tcl-mode-map (make-sparse-keymap))
372   (tcl-fill-mode-map))
373
374 (if inferior-tcl-mode-map
375     ()
376   ;; FIXME Use keymap inheritance here?  FIXME we override comint
377   ;; keybindings here.  Maybe someone has a better set?
378   (setq inferior-tcl-mode-map (copy-keymap comint-mode-map))
379   (tcl-fill-inferior-map))
380
381
382 (defvar inferior-tcl-buffer nil
383   "*The current inferior-tcl process buffer.
384
385 MULTIPLE PROCESS SUPPORT
386 ===========================================================================
387 To run multiple Tcl processes, you start the first up with
388 \\[inferior-tcl].  It will be in a buffer named `*inferior-tcl*'.
389 Rename this buffer with \\[rename-buffer].  You may now start up a new
390 process with another \\[inferior-tcl].  It will be in a new buffer,
391 named `*inferior-tcl*'.  You can switch between the different process
392 buffers with \\[switch-to-buffer].
393
394 Commands that send text from source buffers to Tcl processes -- like
395 `tcl-eval-defun' or `tcl-load-file' -- have to choose a process to
396 send to, when you have more than one Tcl process around.  This is
397 determined by the global variable `inferior-tcl-buffer'.  Suppose you
398 have three inferior Lisps running:
399     Buffer              Process
400     foo                 inferior-tcl
401     bar                 inferior-tcl<2>
402     *inferior-tcl*      inferior-tcl<3>
403 If you do a \\[tcl-eval-defun] command on some Lisp source code, what
404 process do you send it to?
405
406 - If you're in a process buffer (foo, bar, or *inferior-tcl*), 
407   you send it to that process.
408 - If you're in some other buffer (e.g., a source file), you
409   send it to the process attached to buffer `inferior-tcl-buffer'.
410 This process selection is performed by function `inferior-tcl-proc'.
411
412 Whenever \\[inferior-tcl] fires up a new process, it resets
413 `inferior-tcl-buffer' to be the new process's buffer.  If you only run
414 one process, this does the right thing.  If you run multiple
415 processes, you can change `inferior-tcl-buffer' to another process
416 buffer with \\[set-variable].")
417
418 ;;
419 ;; Hooks and other customization.
420 ;;
421
422 (defcustom tcl-mode-hook nil
423   "Hook run on entry to Tcl mode.
424
425 Several functions exist which are useful to run from your
426 `tcl-mode-hook' (see each function's documentation for more
427 information):
428
429   `tcl-guess-application'
430     Guesses a default setting for `tcl-application' based on any
431     \"#!\" line at the top of the file.
432   `tcl-hashify-buffer'
433     Quotes all \"#\" characters that don't correspond to actual
434     Tcl comments.  (Useful when editing code not originally created
435     with this mode).
436   `tcl-auto-fill-mode'
437     Auto-filling of Tcl comments.
438
439 Add functions to the hook with `add-hook':
440
441    (add-hook 'tcl-mode-hook 'tcl-guess-application)
442
443 Emacs 18 users must use `setq' instead:
444
445    (setq tcl-mode-hook (cons 'tcl-guess-application tcl-mode-hook))"
446   :type 'hook
447   :group 'tcl)
448
449
450 (defcustom inferior-tcl-mode-hook nil
451   "Hook for customizing Inferior Tcl mode."
452   :type 'hook
453   :group 'tcl)
454
455 (defvar tcl-proc-list
456   '("proc" "method" "itcl_class" "body" "configbody" "class")
457   "List of commands whose first argument defines something.
458 This exists because some people (eg, me) use `defvar' et al.
459 Call `tcl-set-proc-regexp' and `tcl-set-font-lock-keywords'
460 after changing this list.")
461
462 (defvar tcl-proc-regexp nil
463   "Regexp to use when matching proc headers.")
464
465 (defvar tcl-typeword-list
466   '("global" "upvar" "inherit" "public" "protected" "private"
467     "common" "itk_option" "variable")
468   "List of Tcl keywords denoting \"type\".  Used only for highlighting.
469 Call `tcl-set-font-lock-keywords' after changing this list.")
470
471 ;; Generally I've picked control operators to be keywords.
472 (defvar tcl-keyword-list
473   '("if" "then" "else" "elseif" "for" "foreach" "break" "continue" "while"
474     "eval" "case" "in" "switch" "default" "exit" "error" "proc" "return"
475     "uplevel" "constructor" "destructor" "itcl_class" "loop" "for_array_keys"
476     "for_recursive_glob" "for_file" "method" "body" "configbody" "class")
477   "List of Tcl keywords.  Used only for highlighting.
478 Default list includes some TclX keywords.
479 Call `tcl-set-font-lock-keywords' after changing this list.")
480
481 (defvar tcl-font-lock-keywords nil
482   "Keywords to highlight for Tcl.  See variable `font-lock-keywords'.
483 This variable is generally set from `tcl-proc-regexp',
484 `tcl-typeword-list', and `tcl-keyword-list' by the function
485 `tcl-set-font-lock-keywords'.")
486
487 ;; FIXME need some way to recognize variables because array refs look
488 ;; like 2 sexps.
489 (defvar tcl-type-alist
490   '(
491     ("proc" nil tcl-expr tcl-commands)
492     ("method" nil tcl-expr tcl-commands)
493     ("destructor" tcl-commands)
494     ("constructor" tcl-commands)
495     ("expr" tcl-expr)
496     ("catch" tcl-commands)
497     ("if" tcl-expr "then" tcl-commands)
498     ("elseif" tcl-expr "then" tcl-commands)
499     ("elseif" tcl-expr tcl-commands)
500     ("if" tcl-expr tcl-commands)
501     ("while" tcl-expr tcl-commands)
502     ("for" tcl-commands tcl-expr tcl-commands tcl-commands)
503     ("foreach" nil nil tcl-commands)
504     ("for_file" nil nil tcl-commands)
505     ("for_array_keys" nil nil tcl-commands)
506     ("for_recursive_glob" nil nil nil tcl-commands)
507     ;; Loop handling is not perfect, because the third argument can be
508     ;; either a command or an expr, and there is no real way to look
509     ;; forward.
510     ("loop" nil tcl-expr tcl-expr tcl-commands)
511     ("loop" nil tcl-expr tcl-commands)
512     )
513   "Alist that controls indentation.
514 \(Actually, this really only controls what happens on continuation lines).
515 Each entry looks like `(KEYWORD TYPE ...)'.
516 Each type entry describes a sexp after the keyword, and can be one of:
517 * nil, meaning that this sexp has no particular type.
518 * tcl-expr, meaning that this sexp is an arithmetic expression.
519 * tcl-commands, meaning that this sexp holds Tcl commands.
520 * a string, which must exactly match the string at the corresponding
521   position for a match to be made.
522
523 For example, the entry for the \"loop\" command is:
524
525    (\"loop\" nil tcl-expr tcl-commands)
526
527 This means that the \"loop\" command has three arguments.  The first
528 argument is ignored (for indentation purposes).  The second argument
529 is a Tcl expression, and the last argument is Tcl commands.")
530
531 (defvar tcl-explain-indentation nil
532   "If not `nil', debugging message will be printed during indentation.")
533
534 \f
535
536 ;;
537 ;; Work around differences between various versions of Emacs.
538 ;;
539
540 (defconst tcl-pps-has-arg-6
541   (or tcl-using-emacs-19
542       (and tcl-using-xemacs-19
543            (condition-case nil
544                (progn
545                  (parse-partial-sexp (point) (point) nil nil nil t)
546                  t)
547              (error nil))))
548   "t if Emacs supports COMMENTSTOP argument to `parse-partial-sexp'.")
549
550 ;; Its pretty bogus to have to do this, but there is no easier way to
551 ;; say "match not syntax-1 and not syntax-2".  Too bad you can't put
552 ;; \s in [...].  This sickness is used in Emacs 19 to match a defun
553 ;; starter.  (It is used for this in v18 as well).
554 ;;(defconst tcl-omit-ws-regexp
555 ;;  (concat "^\\(\\s"
556 ;;        (mapconcat 'char-to-string "w_.()\"\\$'/" "\\|\\s")
557 ;;        "\\)\\S(*")
558 ;;  "Regular expression that matches everything except space, comment
559 ;;starter, and comment ender syntax codes.")
560
561 ;; FIXME?  Instead of using the hairy regexp above, we just use a
562 ;; simple one.
563 ;;(defconst tcl-omit-ws-regexp "^[^] \t\n#}]\\S(*"
564 ;;  "Regular expression used in locating function definitions.")
565
566 ;; Here's another stab.  I think this one actually works.  Now the
567 ;; problem seems to be that there is a bug in Emacs 19.22 where
568 ;; end-of-defun doesn't really use the brace matching the one that
569 ;; trails defun-prompt-regexp.
570 ;; ?? Is there a bug now ??
571 (defconst tcl-omit-ws-regexp "^[^ \t\n#}][^\n}]+}*[ \t]+")
572
573 (defun tcl-internal-beginning-of-defun (&optional arg)
574   "Move backward to next beginning of defun.
575 With argument, do this that many times.
576 Returns t unless search stops due to end of buffer."
577   (interactive "p")
578   (if (or (null arg) (= arg 0))
579       (setq arg 1))
580   (let (success)
581     (while (progn
582              (setq arg (1- arg))
583              (and (>= arg 0)
584                   (setq success
585                         (re-search-backward tcl-omit-ws-regexp nil 'move 1))))
586       (while (and (looking-at "[]#}]")
587                   (setq success
588                         (re-search-backward tcl-omit-ws-regexp nil 'move 1)))))
589     (beginning-of-line)
590     (not (null success))))
591
592 (defun tcl-internal-end-of-defun (&optional arg)
593   "Move forward to next end of defun.
594 An end of a defun is found by moving forward from the beginning of one."
595   (interactive "p")
596   (if (or (null arg) (= arg 0)) (setq arg 1))
597   (let ((start (point)))
598     ;; Was forward-char.  I think this works a little better.
599     (forward-line)
600     (tcl-beginning-of-defun)
601     (while (> arg 0)
602       (while (and (re-search-forward tcl-omit-ws-regexp nil 'move 1)
603                   (progn (beginning-of-line) t)
604                   (looking-at "[]#}]")
605                   (progn (forward-line) t)))
606       (let ((next-line (save-excursion 
607                          (forward-line)
608                          (point))))
609         (while (< (point) next-line)
610           (forward-sexp)))
611       (forward-line)
612       (if (> (point) start) (setq arg (1- arg))))))
613
614 ;; We can now use begining-of-defun as long as we set up a
615 ;; certain regexp.  In Emacs 18, we need our own function.
616 (defalias 'tcl-beginning-of-defun
617   (if tcl-using-emacs-19
618       'beginning-of-defun
619     'tcl-internal-beginning-of-defun))
620
621 ;; Ditto end-of-defun.
622 (defalias 'tcl-end-of-defun
623   (if (and tcl-using-emacs-19 (not tcl-using-xemacs-19))
624       'end-of-defun
625     'tcl-internal-end-of-defun))
626
627 ;; Internal mark-defun that is used for losing Emacsen.
628 (defun tcl-internal-mark-defun ()
629   "Put mark at end of Tcl function, point at beginning."
630   (interactive)
631   (push-mark (point))
632   (tcl-end-of-defun)
633   (if tcl-using-emacs-19
634       (push-mark (point) nil t)
635     (push-mark (point)))
636   (tcl-beginning-of-defun)
637   (backward-paragraph))
638
639 ;; In Emacs 19.23 and later, mark-defun works as advertised.  I
640 ;; don't know about XEmacs, so for now it and Emacs 18 just lose.
641 (fset 'tcl-mark-defun
642       (if tcl-using-emacs-19-23
643           'mark-defun
644         'tcl-internal-mark-defun))
645
646 ;; In Emacs 19, mark takes an additional "force" argument.  I
647 ;; don't know about XEmacs, so I'm just assuming it is the same.
648 ;; Emacs 18 doesn't have this argument.
649 (defun tcl-mark ()
650   "Return mark, or nil if none."
651   (if tcl-using-emacs-19
652       (mark t)
653     (mark)))
654
655 \f
656
657 ;;
658 ;; Some helper functions.
659 ;;
660
661 (defun tcl-set-proc-regexp ()
662   "Set `tcl-proc-regexp' from variable `tcl-proc-list'."
663   (setq tcl-proc-regexp (concat "^\\s-*\\("
664                                 (mapconcat 'identity tcl-proc-list "\\|")
665                                 "\\)[ \t]+")))
666
667 (defun tcl-set-font-lock-keywords ()
668   "Set `tcl-font-lock-keywords'.
669 Uses variables `tcl-proc-regexp' and `tcl-keyword-list'."
670   (setq tcl-font-lock-keywords
671         (list
672          ;; Names of functions (and other "defining things").
673          (list (concat tcl-proc-regexp "\\([^ \t\n]+\\)")
674                2 'font-lock-function-name-face)
675
676          ;; Names of type-defining things.
677          (list (concat "\\(\\s-\\|^\\)\\("
678                        ;; FIXME Use 'regexp-quote?
679                        (mapconcat 'identity tcl-typeword-list "\\|")
680                        "\\)\\(\\s-\\|$\\)")
681                2 'font-lock-type-face)
682
683          ;; Keywords.  Only recognized if surrounded by whitespace.
684          ;; FIXME consider using "not word or symbol", not
685          ;; "whitespace".
686          (cons (concat "\\(\\s-\\|^\\)\\("
687                        ;; FIXME Use regexp-quote? 
688                        (mapconcat 'identity tcl-keyword-list "\\|")
689                        "\\)\\(\\s-\\|$\\)")
690                2)
691          )))
692
693 (if tcl-proc-regexp
694     ()
695   (tcl-set-proc-regexp))
696
697 (if tcl-font-lock-keywords
698     ()
699   (tcl-set-font-lock-keywords))
700
701 ;; XEmacs change.
702 (defvar tcl-imenu-generic-expression
703   '((nil "^proc[ \t]+\\([-A-Za-z0-9_:+*]+\\)" 1))
704   "Imenu generic expression for `tcl-mode'.  See `imenu-generic-expression'.")
705 \f
706
707 ;;
708 ;; The mode itself.
709 ;;
710
711 ;;;###autoload
712 (defun tcl-mode ()
713   "Major mode for editing Tcl code.
714 Expression and list commands understand all Tcl brackets.
715 Tab indents for Tcl code.
716 Paragraphs are separated by blank lines only.
717 Delete converts tabs to spaces as it moves back.
718
719 Variables controlling indentation style:
720   `tcl-indent-level'
721     Indentation of Tcl statements within surrounding block.
722   `tcl-continued-indent-level'
723     Indentation of continuation line relative to first line of command.
724
725 Variables controlling user interaction with mode (see variable
726 documentation for details):
727   `tcl-tab-always-indent'
728     Controls action of TAB key.
729   `tcl-auto-newline'
730     Non-nil means automatically newline before and after braces, brackets,
731     and semicolons inserted in Tcl code.
732   `tcl-electric-hash-style'
733     Controls action of `#' key.
734   `tcl-use-hairy-comment-detector'
735     If t, use more complicated, but slower, comment detector.
736     This variable is only used in Emacs 19.
737   `tcl-use-smart-word-finder'
738     If not nil, use a smarter, Tcl-specific way to find the current
739     word when looking up help on a Tcl command.
740
741 Turning on Tcl mode calls the value of the variable `tcl-mode-hook'
742 with no args, if that value is non-nil.  Read the documentation for
743 `tcl-mode-hook' to see what kinds of interesting hook functions
744 already exist.
745
746 Commands:
747 \\{tcl-mode-map}"
748   (interactive)
749   (kill-all-local-variables)
750   (use-local-map tcl-mode-map)
751   (setq major-mode 'tcl-mode)
752   (setq mode-name "Tcl")
753   (setq local-abbrev-table tcl-mode-abbrev-table)
754   (set-syntax-table tcl-mode-syntax-table)
755
756   (make-local-variable 'paragraph-start)
757   (make-local-variable 'paragraph-separate)
758   (if (and tcl-using-emacs-19-23
759            (>= emacs-minor-version 29))
760       (progn
761         ;; In Emacs 19.29, you aren't supposed to start these with a ^.
762         (setq paragraph-start "$\\|\f")
763         (setq paragraph-separate paragraph-start))
764     (setq paragraph-start (concat "^$\\|" page-delimiter))
765     (setq paragraph-separate paragraph-start))
766   (make-local-variable 'paragraph-ignore-fill-prefix)
767   (setq paragraph-ignore-fill-prefix t)
768
769   ;; XEmacs change: check for filladapt-mode.
770   (if (not (and (boundp 'filladapt-mode)
771                 filladapt-mode))
772       (progn
773         (make-local-variable 'fill-paragraph-function)
774         (setq fill-paragraph-function 'tcl-do-fill-paragraph)))
775
776   (make-local-variable 'indent-line-function)
777   (setq indent-line-function 'tcl-indent-line)
778
779   ;; Tcl doesn't require a final newline.
780   ;; (make-local-variable 'require-final-newline)
781   ;; (setq require-final-newline t)
782
783   (make-local-variable 'comment-start)
784   (setq comment-start "# ")
785   (make-local-variable 'comment-start-skip)
786   (setq comment-start-skip "#+ *")
787   (make-local-variable 'comment-column)
788   (setq comment-column 40)
789   (make-local-variable 'comment-end)
790   (setq comment-end "")
791
792   (make-local-variable 'outline-regexp)
793   (setq outline-regexp "[^\n\^M]")
794   (make-local-variable 'outline-level)
795   (setq outline-level 'tcl-outline-level)
796
797   (make-local-variable 'font-lock-defaults)
798   (setq font-lock-defaults
799         '(tcl-font-lock-keywords))
800
801   ;; XEmacs change.
802   (make-local-variable 'imenu-generic-expression)
803   (setq imenu-generic-expression tcl-imenu-generic-expression)
804   ;; (add-hook 'tcl-mode-hook 'imenu-add-menubar-index)
805
806   (make-local-variable 'parse-sexp-ignore-comments)
807
808   ;; Settings for new dabbrev code.
809   (make-local-variable 'dabbrev-case-fold-search)
810   (setq dabbrev-case-fold-search nil)
811   (make-local-variable 'dabbrev-case-replace)
812   (setq dabbrev-case-replace nil)
813   (make-local-variable 'dabbrev-abbrev-skip-leading-regexp)
814   (setq dabbrev-abbrev-skip-leading-regexp "[$!]")
815   (make-local-variable 'dabbrev-abbrev-char-regexp)
816   (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
817
818   (if tcl-using-emacs-19
819       (progn
820         ;; This can only be set to t in Emacs 19 and XEmacs.
821         ;; Emacs 18 and Epoch lose.
822         (setq parse-sexp-ignore-comments t)
823         ;; XEmacs has defun-prompt-regexp, but I don't believe
824         ;; that it works for end-of-defun -- only for
825         ;; beginning-of-defun.
826         (make-local-variable 'defun-prompt-regexp)
827         (setq defun-prompt-regexp tcl-omit-ws-regexp)
828         ;; The following doesn't work in Lucid Emacs 19.6, but maybe
829         ;; it will appear in later versions.
830         (make-local-variable 'add-log-current-defun-function)
831         (setq add-log-current-defun-function 'add-log-tcl-defun))
832     (setq parse-sexp-ignore-comments nil))
833
834   ;; Put Tcl menu into menubar for XEmacs.  This happens
835   ;; automatically in Emacs.
836   (if (and tcl-using-xemacs-19
837            current-menubar
838            (not (assoc "Tcl" current-menubar)))
839       (progn
840         (set-buffer-menubar (copy-sequence current-menubar))
841         (add-menu nil "Tcl" tcl-xemacs-menu)))
842   ;; Append Tcl menu to popup menu for XEmacs.
843   (if (and tcl-using-xemacs-19 (boundp 'mode-popup-menu))
844       (setq mode-popup-menu
845             (cons (concat mode-name " Mode Commands") tcl-xemacs-menu)))
846
847   ;; If hilit19 is loaded, add our stuff.
848   (if (featurep 'hilit19)
849       (tcl-hilit))
850
851   (run-hooks 'tcl-mode-hook))
852
853 \f
854
855 ;; This is used for braces, brackets, and semi (except for closing
856 ;; braces, which are handled specially).
857 (defun tcl-electric-char (arg)
858   "Insert character and correct line's indentation."
859   (interactive "p")
860   ;; Indent line first; this looks better if parens blink.
861   (tcl-indent-line)
862   (self-insert-command arg)
863   (if (and tcl-auto-newline (= last-command-char ?\;))
864       (progn
865         (newline)
866         (tcl-indent-line))))
867
868 ;; This is used for closing braces.  If tcl-auto-newline is set, can
869 ;; insert a newline both before and after the brace, depending on
870 ;; context.  FIXME should this be configurable?  Does anyone use this?
871 (defun tcl-electric-brace (arg)
872   "Insert character and correct line's indentation."
873   (interactive "p")
874   ;; If auto-newlining and there is stuff on the same line, insert a
875   ;; newline first.
876   (if tcl-auto-newline
877       (progn
878         (if (save-excursion
879               (skip-chars-backward " \t")
880               (bolp))
881             ()
882           (tcl-indent-line)
883           (newline))
884         ;; In auto-newline case, must insert a newline after each
885         ;; brace.  So an explicit loop is needed.
886         (while (> arg 0)
887           (insert last-command-char)
888           (tcl-indent-line)
889           (newline)
890           (setq arg (1- arg))))
891     (self-insert-command arg))
892   (tcl-indent-line))
893
894 \f
895
896 (defun tcl-indent-command (&optional arg)
897   "Indent current line as Tcl code, or in some cases insert a tab character.
898 If `tcl-tab-always-indent' is t (the default), always indent current line.
899 If `tcl-tab-always-indent' is nil and point is not in the indentation
900 area at the beginning of the line, a TAB is inserted.
901 Other values of `tcl-tab-always-indent' cause the first possible action
902 from the following list to take place:
903
904   1. Move from beginning of line to correct indentation.
905   2. Delete an empty comment.
906   3. Move forward to start of comment, indenting if necessary.
907   4. Move forward to end of line, indenting if necessary.
908   5. Create an empty comment.
909   6. Move backward to start of comment, indenting if necessary."
910   (interactive "p")
911   (cond
912    ((not tcl-tab-always-indent)
913     ;; Indent if in indentation area, otherwise insert TAB.
914     (if (<= (current-column) (current-indentation))
915         (tcl-indent-line)
916       (insert-tab arg)))
917    ((eq tcl-tab-always-indent t)
918     ;; Always indent.
919     (tcl-indent-line))
920    (t
921     ;; "Perl-mode" style TAB command.
922     (let* ((ipoint (point))
923            (eolpoint (progn
924                        (end-of-line)
925                        (point)))
926            (comment-p (tcl-in-comment)))
927       (cond
928        ((= ipoint (save-excursion
929                     (beginning-of-line)
930                     (point)))
931         (beginning-of-line)
932         (tcl-indent-line)
933         ;; If indenting didn't leave us in column 0, go to the
934         ;; indentation.  Otherwise leave point at end of line.  This
935         ;; is a hack.
936         (if (= (point) (save-excursion
937                          (beginning-of-line)
938                          (point)))
939             (end-of-line)
940           (back-to-indentation)))
941        ((and comment-p (looking-at "[ \t]*$"))
942         ;; Empty comment, so delete it.  We also delete any ";"
943         ;; characters at the end of the line.  I think this is
944         ;; friendlier, but I don't know how other people will feel.
945         (backward-char)
946         (skip-chars-backward " \t;")
947         (delete-region (point) eolpoint))
948        ((and comment-p (< ipoint (point)))
949         ;; Before comment, so skip to it.
950         (tcl-indent-line)
951         (indent-for-comment))
952        ((/= ipoint eolpoint)
953         ;; Go to end of line (since we're not there yet).
954         (goto-char eolpoint)
955         (tcl-indent-line))
956        ((not comment-p)
957         (tcl-indent-line)
958         (tcl-indent-for-comment))
959        (t
960         ;; Go to start of comment.  We don't leave point where it is
961         ;; because we want to skip comment-start-skip.
962         (tcl-indent-line)
963         (indent-for-comment)))))))
964
965 (defun tcl-indent-line ()
966   "Indent current line as Tcl code.
967 Return the amount the indentation changed by."
968   (let ((indent (calculate-tcl-indent nil))
969         beg shift-amt
970         (case-fold-search nil)
971         (pos (- (point-max) (point))))
972     (beginning-of-line)
973     (setq beg (point))
974     (cond ((eq indent nil)
975            (setq indent (current-indentation)))
976           (t
977            (skip-chars-forward " \t")
978            (if (listp indent) (setq indent (car indent)))
979            (cond ((= (following-char) ?})
980                   (setq indent (- indent tcl-indent-level)))
981                  ((= (following-char) ?\])
982                   (setq indent (- indent 1))))))
983     (skip-chars-forward " \t")
984     (setq shift-amt (- indent (current-column)))
985     (if (zerop shift-amt)
986         (if (> (- (point-max) pos) (point))
987             (goto-char (- (point-max) pos)))
988       (delete-region beg (point))
989       (indent-to indent)
990       ;; If initial point was within line's indentation,
991       ;; position after the indentation.  Else stay at same point in text.
992       (if (> (- (point-max) pos) (point))
993           (goto-char (- (point-max) pos))))
994     shift-amt))
995
996 (defun tcl-figure-type ()
997   "Determine type of sexp at point.
998 This is either `tcl-expr', `tcl-commands', or nil.  Puts point at start
999 of sexp that indicates types.
1000
1001 See documentation for variable `tcl-type-alist' for more information."
1002   (let ((count 0)
1003         result
1004         word-stack)
1005     (while (and (< count 5)
1006                 (not result))
1007       (condition-case nil
1008           (progn
1009             ;; FIXME should use "tcl-backward-sexp", which would skip
1010             ;; over entire variables, etc.
1011             (backward-sexp)
1012             (if (looking-at "[a-zA-Z_]+")
1013                 (let ((list tcl-type-alist)
1014                       entry)
1015                   (setq word-stack (cons (tcl-word-no-props) word-stack))
1016                   (while (and list (not result))
1017                     (setq entry (car list))
1018                     (setq list (cdr list))
1019                     (let ((index 0))
1020                       (while (and entry (<= index count))
1021                         ;; Abort loop if string does not match word on
1022                         ;; stack.
1023                         (and (stringp (car entry))
1024                              (not (string= (car entry)
1025                                            (nth index word-stack)))
1026                              (setq entry nil))
1027                         (setq entry (cdr entry))
1028                         (setq index (1+ index)))
1029                       (and (> index count)
1030                            (not (stringp (car entry)))
1031                            (setq result (car entry)))
1032                       )))
1033               (setq word-stack (cons nil word-stack))))
1034         (error nil))
1035       (setq count (1+ count)))
1036     (and tcl-explain-indentation
1037          (message "Indentation type %s" result))
1038     result))
1039
1040 (defun calculate-tcl-indent (&optional parse-start)
1041   "Return appropriate indentation for current line as Tcl code.
1042 In usual case returns an integer: the column to indent to.
1043 Returns nil if line starts inside a string, t if in a comment."
1044   (save-excursion
1045     (beginning-of-line)
1046     (let* ((indent-point (point))
1047            (case-fold-search nil)
1048            (continued-line 
1049             (save-excursion
1050               (if (bobp)
1051                   nil
1052                 (backward-char)
1053                 (= ?\\ (preceding-char)))))
1054            (continued-indent-value (if continued-line
1055                                        tcl-continued-indent-level
1056                                      0))
1057            state
1058            containing-sexp
1059            found-next-line)
1060       (if parse-start
1061           (goto-char parse-start)
1062         (tcl-beginning-of-defun))
1063       (while (< (point) indent-point)
1064         (setq parse-start (point))
1065         (setq state (parse-partial-sexp (point) indent-point 0))
1066         (setq containing-sexp (car (cdr state))))
1067       (cond ((or (nth 3 state) (nth 4 state))
1068              ;; Inside comment or string.  Return nil or t if should
1069              ;; not change this line
1070              (nth 4 state))
1071             ((null containing-sexp)
1072              ;; Line is at top level.
1073              continued-indent-value)
1074             (t
1075              ;; Set expr-p if we are looking at the expression part of
1076              ;; an "if", "expr", etc statement.  Set commands-p if we
1077              ;; are looking at the body part of an if, while, etc
1078              ;; statement.  FIXME Should check for "for" loops here.
1079              (goto-char containing-sexp)
1080              (let* ((sexpr-type (tcl-figure-type))
1081                     (expr-p (eq sexpr-type 'tcl-expr))
1082                     (commands-p (eq sexpr-type 'tcl-commands))
1083                     (expr-start (point)))
1084                ;; Find the first statement in the block and indent
1085                ;; like it.  The first statement in the block might be
1086                ;; on the same line, so what we do is skip all
1087                ;; "virtually blank" lines, looking for a non-blank
1088                ;; one.  A line is virtually blank if it only contains
1089                ;; a comment and whitespace.  FIXME continued comments
1090                ;; aren't supported.  They are a wart on Tcl anyway.
1091                ;; We do it this funky way because we want to know if
1092                ;; we've found a statement on some line _after_ the
1093                ;; line holding the sexp opener.
1094                (goto-char containing-sexp)
1095                (forward-char)
1096                (if (and (< (point) indent-point)
1097                         (looking-at "[ \t]*\\(#.*\\)?$"))
1098                    (progn
1099                      (forward-line)
1100                      (while (and (< (point) indent-point)
1101                                  (looking-at "[ \t]*\\(#.*\\)?$"))
1102                        (setq found-next-line t)
1103                        (forward-line))))
1104                (if (or continued-line
1105                        (/= (char-after containing-sexp) ?{)
1106                        expr-p)
1107                    (progn
1108                      ;; Line is continuation line, or the sexp opener
1109                      ;; is not a curly brace, or we are are looking at
1110                      ;; an `expr' expression (which must be split
1111                      ;; specially).  So indentation is column of first
1112                      ;; good spot after sexp opener (with some added
1113                      ;; in the continued-line case).  If there is no
1114                      ;; nonempty line before the indentation point, we
1115                      ;; use the column of the character after the sexp
1116                      ;; opener.
1117                      (if (>= (point) indent-point)
1118                          (progn
1119                            (goto-char containing-sexp)
1120                            (forward-char))
1121                        (skip-chars-forward " \t"))
1122                      (+ (current-column) continued-indent-value))
1123                  ;; After a curly brace, and not a continuation line.
1124                  ;; So take indentation from first good line after
1125                  ;; start of block, unless that line is on the same
1126                  ;; line as the opening brace.  In this case use the
1127                  ;; indentation of the opening brace's line, plus
1128                  ;; another indent step.  If we are in the body part
1129                  ;; of an "if" or "while" then the indentation is
1130                  ;; taken from the line holding the start of the
1131                  ;; statement.
1132                  (if (and (< (point) indent-point)
1133                           found-next-line)
1134                      (current-indentation)
1135                    (if commands-p
1136                        (goto-char expr-start)
1137                      (goto-char containing-sexp))
1138                    (+ (current-indentation) tcl-indent-level)))))))))
1139
1140 \f
1141
1142 (defun indent-tcl-exp ()
1143   "Indent each line of the Tcl grouping following point."
1144   (interactive)
1145   (let ((indent-stack (list nil))
1146         (contain-stack (list (point)))
1147         (case-fold-search nil)
1148         outer-loop-done inner-loop-done state ostate
1149         this-indent last-sexp continued-line
1150         (next-depth 0)
1151         last-depth)
1152     (save-excursion
1153       (forward-sexp 1))
1154     (save-excursion
1155       (setq outer-loop-done nil)
1156       (while (and (not (eobp)) (not outer-loop-done))
1157         (setq last-depth next-depth)
1158         ;; Compute how depth changes over this line
1159         ;; plus enough other lines to get to one that
1160         ;; does not end inside a comment or string.
1161         ;; Meanwhile, do appropriate indentation on comment lines.
1162         (setq inner-loop-done nil)
1163         (while (and (not inner-loop-done)
1164                     (not (and (eobp) (setq outer-loop-done t))))
1165           (setq ostate state)
1166           (setq state (parse-partial-sexp (point) (progn (end-of-line) (point))
1167                                           nil nil state))
1168           (setq next-depth (car state))
1169           (if (and (car (cdr (cdr state)))
1170                    (>= (car (cdr (cdr state))) 0))
1171               (setq last-sexp (car (cdr (cdr state)))))
1172           (if (or (nth 4 ostate))
1173               (tcl-indent-line))
1174           (if (or (nth 3 state))
1175               (forward-line 1)
1176             (setq inner-loop-done t)))
1177         (if (<= next-depth 0)
1178             (setq outer-loop-done t))
1179         (if outer-loop-done
1180             nil
1181           ;; If this line had ..))) (((.. in it, pop out of the levels
1182           ;; that ended anywhere in this line, even if the final depth
1183           ;; doesn't indicate that they ended.
1184           (while (> last-depth (nth 6 state))
1185             (setq indent-stack (cdr indent-stack)
1186                   contain-stack (cdr contain-stack)
1187                   last-depth (1- last-depth)))
1188           (if (/= last-depth next-depth)
1189               (setq last-sexp nil))
1190           ;; Add levels for any parens that were started in this line.
1191           (while (< last-depth next-depth)
1192             (setq indent-stack (cons nil indent-stack)
1193                   contain-stack (cons nil contain-stack)
1194                   last-depth (1+ last-depth)))
1195           (if (null (car contain-stack))
1196               (setcar contain-stack 
1197                       (or (car (cdr state))
1198                           (save-excursion
1199                             (forward-sexp -1)
1200                             (point)))))
1201           (forward-line 1)
1202           (setq continued-line 
1203                 (save-excursion
1204                   (backward-char)
1205                   (= (preceding-char) ?\\)))
1206           (skip-chars-forward " \t")
1207           (if (eolp)
1208               nil
1209             (if (and (car indent-stack)
1210                      (>= (car indent-stack) 0))
1211                 ;; Line is on an existing nesting level.
1212                 (setq this-indent (car indent-stack))
1213               ;; Just started a new nesting level.
1214               ;; Compute the standard indent for this level.
1215               (let ((val (calculate-tcl-indent
1216                           (if (car indent-stack)
1217                               (- (car indent-stack))))))
1218                 (setcar indent-stack
1219                         (setq this-indent val))
1220                 (setq continued-line nil)))
1221             (cond ((not (numberp this-indent)))
1222                   ((= (following-char) ?})
1223                    (setq this-indent (- this-indent tcl-indent-level)))
1224                   ((= (following-char) ?\])
1225                    (setq this-indent (- this-indent 1))))
1226             ;; Put chosen indentation into effect.
1227             (or (null this-indent)
1228                 (= (current-column) 
1229                    (if continued-line 
1230                        (+ this-indent tcl-indent-level)
1231                      this-indent))
1232                 (progn
1233                   (delete-region (point) (progn (beginning-of-line) (point)))
1234                   (indent-to 
1235                    (if continued-line 
1236                        (+ this-indent tcl-indent-level)
1237                      this-indent)))))))))
1238   )
1239
1240 \f
1241
1242 ;;
1243 ;; Interfaces to other packages.
1244 ;;
1245
1246 ;; FIXME Definition of function is very ad-hoc.  Should use
1247 ;; tcl-beginning-of-defun.  Also has incestuous knowledge about the
1248 ;; format of tcl-proc-regexp.
1249 (defun add-log-tcl-defun ()
1250   "Return name of Tcl function point is in, or nil."
1251   (save-excursion
1252     (end-of-line)
1253     (if (re-search-backward (concat tcl-proc-regexp "\\([^ \t\n{]+\\)") nil t)
1254         (buffer-substring (match-beginning 2)
1255                           (match-end 2)))))
1256
1257 (defun tcl-outline-level ()
1258   (save-excursion
1259     (skip-chars-forward " \t")
1260     (current-column)))
1261
1262 \f
1263
1264 ;;
1265 ;; Helper functions for inferior Tcl mode.
1266 ;;
1267
1268 ;; This exists to let us delete the prompt when commands are sent
1269 ;; directly to the inferior Tcl.  See gud.el for an explanation of how
1270 ;; it all works (I took it from there).  This stuff doesn't really
1271 ;; work as well as I'd like it to.  But I don't believe there is
1272 ;; anything useful that can be done.
1273 (defvar inferior-tcl-delete-prompt-marker nil)
1274
1275 (defun tcl-filter (proc string)
1276   (let ((inhibit-quit t))
1277     (save-excursion
1278       (set-buffer (process-buffer proc))
1279       (goto-char (process-mark proc))
1280       ;; Delete prompt if requested.
1281       (if (marker-buffer inferior-tcl-delete-prompt-marker)
1282           (progn
1283             (delete-region (point) inferior-tcl-delete-prompt-marker)
1284             (set-marker inferior-tcl-delete-prompt-marker nil)))))
1285   (if (fboundp 'comint-output-filter)
1286       (comint-output-filter proc string)
1287     (funcall comint-output-filter string)))
1288
1289 (defun tcl-send-string (proc string)
1290   (save-excursion
1291     (set-buffer (process-buffer proc))
1292     (goto-char (process-mark proc))
1293     (beginning-of-line)
1294     (if (looking-at comint-prompt-regexp)
1295         (set-marker inferior-tcl-delete-prompt-marker (point))))
1296   (comint-send-string proc string))
1297
1298 (defun tcl-send-region (proc start end)
1299   (save-excursion
1300     (set-buffer (process-buffer proc))
1301     (goto-char (process-mark proc))
1302     (beginning-of-line)
1303     (if (looking-at comint-prompt-regexp)
1304         (set-marker inferior-tcl-delete-prompt-marker (point))))
1305   (comint-send-region proc start end))
1306
1307 (defun switch-to-tcl (eob-p)
1308   "Switch to inferior Tcl process buffer.
1309 With argument, positions cursor at end of buffer."
1310   (interactive "P")
1311   (if (get-buffer inferior-tcl-buffer)
1312       (pop-to-buffer inferior-tcl-buffer)
1313     (error "No current inferior Tcl buffer"))
1314   (cond (eob-p
1315          (push-mark)
1316          (goto-char (point-max)))))
1317
1318 (defun inferior-tcl-proc ()
1319   "Return current inferior Tcl process.
1320 See variable `inferior-tcl-buffer'."
1321   (let ((proc (get-buffer-process (if (eq major-mode 'inferior-tcl-mode)
1322                                       (current-buffer)
1323                                     inferior-tcl-buffer))))
1324     (or proc
1325         (error "No Tcl process; see variable `inferior-tcl-buffer'"))))
1326
1327 (defun tcl-eval-region (start end &optional and-go)
1328   "Send the current region to the inferior Tcl process.
1329 Prefix argument means switch to the Tcl buffer afterwards."
1330   (interactive "r\nP")
1331   (let ((proc (inferior-tcl-proc)))
1332     (tcl-send-region proc start end)
1333     (tcl-send-string proc "\n")
1334     (if and-go (switch-to-tcl t))))
1335
1336 (defun tcl-eval-defun (&optional and-go)
1337   "Send the current defun to the inferior Tcl process.
1338 Prefix argument means switch to the Tcl buffer afterwards."
1339   (interactive "P")
1340   (save-excursion
1341     (tcl-end-of-defun)
1342     (let ((end (point)))
1343       (tcl-beginning-of-defun)
1344       (tcl-eval-region (point) end)))
1345   (if and-go (switch-to-tcl t)))
1346
1347 \f
1348
1349 ;;
1350 ;; Inferior Tcl mode itself.
1351 ;;
1352
1353 (defun inferior-tcl-mode ()
1354   "Major mode for interacting with Tcl interpreter.
1355
1356 A Tcl process can be started with \\[inferior-tcl].
1357
1358 Entry to this mode runs the normal hooks `comint-mode-hook' and
1359 `inferior-tcl-mode-hook', in that order.
1360
1361 You can send text to the inferior Tcl process from other buffers
1362 containing Tcl source.
1363
1364 Variables controlling Inferior Tcl mode:
1365   `tcl-application'
1366     Name of program to run.
1367   `tcl-command-switches'
1368     Command line arguments to `tcl-application'.
1369   `tcl-prompt-regexp'
1370     Matches prompt.
1371   `inferior-tcl-source-command'
1372     Command to use to read Tcl file in running application.
1373   `inferior-tcl-buffer'
1374     The current inferior Tcl process buffer.  See variable
1375     documentation for details on multiple-process support.
1376
1377 The following commands are available:
1378 \\{inferior-tcl-mode-map}"
1379   (interactive)
1380   (comint-mode)
1381   (setq comint-prompt-regexp (or tcl-prompt-regexp
1382                                  (concat "^"
1383                                          (regexp-quote tcl-application)
1384                                          ">")))
1385   (setq major-mode 'inferior-tcl-mode)
1386   (setq mode-name "Inferior Tcl")
1387   (if (boundp 'modeline-process)
1388       (setq modeline-process '(": %s")) ; For XEmacs.
1389     (setq mode-line-process '(": %s")))
1390   (use-local-map inferior-tcl-mode-map)
1391   (setq local-abbrev-table tcl-mode-abbrev-table)
1392   (set-syntax-table tcl-mode-syntax-table)
1393   (if tcl-using-emacs-19
1394       (progn
1395         (make-local-variable 'defun-prompt-regexp)
1396         (setq defun-prompt-regexp tcl-omit-ws-regexp)))
1397   (make-local-variable 'inferior-tcl-delete-prompt-marker)
1398   (setq inferior-tcl-delete-prompt-marker (make-marker))
1399   (set-process-filter (get-buffer-process (current-buffer)) 'tcl-filter)
1400   (run-hooks 'inferior-tcl-mode-hook))
1401
1402 ;;;###autoload
1403 (defun inferior-tcl (cmd)
1404   "Run inferior Tcl process.
1405 Prefix arg means enter program name interactively.
1406 See documentation for function `inferior-tcl-mode' for more information."
1407   (interactive
1408    (list (if current-prefix-arg
1409              (read-string "Run Tcl: " tcl-application)
1410            tcl-application)))
1411   (if (not (comint-check-proc "*inferior-tcl*"))
1412       (progn
1413         (set-buffer (apply (function make-comint) "inferior-tcl" cmd nil
1414                            tcl-command-switches))
1415         (inferior-tcl-mode)))
1416   (make-local-variable 'tcl-application)
1417   (setq tcl-application cmd)
1418   (setq inferior-tcl-buffer "*inferior-tcl*")
1419   (switch-to-buffer "*inferior-tcl*"))
1420
1421 (and (fboundp 'defalias)
1422      (defalias 'run-tcl 'inferior-tcl))
1423
1424 \f
1425
1426 ;;
1427 ;; Auto-fill support.
1428 ;;
1429
1430 (defun tcl-real-command-p ()
1431   "Return nil if point is not at the beginning of a command.
1432 A command is the first word on an otherwise empty line, or the
1433 first word following a semicolon, opening brace, or opening bracket."
1434   (save-excursion
1435     (skip-chars-backward " \t")
1436     (cond
1437      ((bobp) t)
1438      ((bolp)
1439       (backward-char)
1440       ;; Note -- continued comments are not supported here.  I
1441       ;; consider those to be a wart on the language.
1442       (not (eq ?\\ (preceding-char))))
1443      (t
1444       (memq (preceding-char) '(?\; ?{ ?\[))))))
1445
1446 ;; FIXME doesn't actually return t.  See last case.
1447 (defun tcl-real-comment-p ()
1448   "Return t if point is just after the `#' beginning a real comment.
1449 Does not check to see if previous char is actually `#'.
1450 A real comment is either at the beginning of the buffer,
1451 preceded only by whitespace on the line, or has a preceding
1452 semicolon, opening brace, or opening bracket on the same line."
1453   (save-excursion
1454     (backward-char)
1455     (tcl-real-command-p)))
1456
1457 (defun tcl-hairy-scan-for-comment (state end always-stop)
1458   "Determine if point is in a comment.
1459 Returns a list of the form `(FLAG . STATE)'.  STATE can be used
1460 as input to future invocations.  FLAG is nil if not in comment,
1461 t otherwise.  If in comment, leaves point at beginning of comment.
1462
1463 This function does not work in Emacs 18.
1464 See also `tcl-simple-scan-for-comment', a
1465 simpler version that is often right, and works in Emacs 18."
1466   (let ((bol (save-excursion
1467                (goto-char end)
1468                (beginning-of-line)
1469                (point)))
1470         real-comment
1471         last-cstart)
1472     (while (and (not last-cstart) (< (point) end))
1473       (setq real-comment nil)           ;In case we've looped around and it is
1474                                         ;set.
1475       (setq state (parse-partial-sexp (point) end nil nil state t))
1476       (if (nth 4 state)
1477           (progn
1478             ;; If ALWAYS-STOP is set, stop even if we don't have a
1479             ;; real comment, or if the comment isn't on the same line
1480             ;; as the end.
1481             (if always-stop (setq last-cstart (point)))
1482             ;; If we have a real comment, then set the comment
1483             ;; starting point if we are on the same line as the ending
1484             ;; location.
1485             (setq real-comment (tcl-real-comment-p))
1486             (if real-comment
1487                 (progn
1488                   (and (> (point) bol) (setq last-cstart (point)))
1489                   ;; NOTE Emacs 19 has a misfeature whereby calling
1490                   ;; parse-partial-sexp with COMMENTSTOP set and with
1491                   ;; an initial list that says point is in a comment
1492                   ;; will cause an immediate return.  So we must skip
1493                   ;; over the comment ourselves.
1494                   (beginning-of-line 2)))
1495             ;; Frob the state to make it look like we aren't in a
1496             ;; comment.
1497             (setcar (nthcdr 4 state) nil))))
1498     (and last-cstart
1499          (goto-char last-cstart))
1500     (cons real-comment state)))
1501
1502 (defun tcl-hairy-in-comment ()
1503   "Return t if point is in a comment, and leave point at beginning of comment."
1504   (let ((save (point)))
1505     (tcl-beginning-of-defun)
1506     (car (tcl-hairy-scan-for-comment nil save nil))))
1507
1508 (defun tcl-simple-in-comment ()
1509   "Return t if point is in comment, and leave point at beginning of comment.
1510 This is faster that `tcl-hairy-in-comment', but is correct less often."
1511   (let ((save (point))
1512         comment)
1513     (beginning-of-line)
1514     (while (and (< (point) save) (not comment))
1515       (search-forward "#" save 'move)
1516       (setq comment (tcl-real-comment-p)))
1517     comment))
1518
1519 (defun tcl-in-comment ()
1520   "Return t if point is in comment, and leave point at beginning of comment."
1521   (if (and tcl-pps-has-arg-6
1522            tcl-use-hairy-comment-detector)
1523       (tcl-hairy-in-comment)
1524     (tcl-simple-in-comment)))
1525
1526 (defun tcl-do-fill-paragraph (ignore)
1527   "`fill-paragraph' function for Tcl mode.  Only fills in a comment."
1528   (let (in-comment col where)
1529     (save-excursion
1530       (end-of-line)
1531       (setq in-comment (tcl-in-comment))
1532       (if in-comment
1533           (progn
1534             (setq where (1+ (point)))
1535             (setq col (1- (current-column))))))
1536     (and in-comment
1537          (save-excursion
1538            (back-to-indentation)
1539            (= col (current-column)))
1540          ;; In a comment.  Set the fill prefix, and find the paragraph
1541          ;; boundaries by searching for lines that look like
1542          ;; comment-only lines.
1543          (let ((fill-prefix (buffer-substring (progn
1544                                                 (beginning-of-line)
1545                                                 (point))
1546                                               where))
1547                p-start p-end)
1548            ;; Search backwards.
1549            (save-excursion
1550              (while (looking-at "^[ \t]*#")
1551                (forward-line -1))
1552              (forward-line)
1553              (setq p-start (point)))
1554
1555            ;; Search forwards.
1556            (save-excursion
1557              (while (looking-at "^[ \t]*#")
1558                (forward-line))
1559              (setq p-end (point)))
1560
1561            ;; Narrow and do the fill.
1562            (save-restriction
1563              (narrow-to-region p-start p-end)
1564              (fill-paragraph ignore)))))
1565   t)
1566
1567 (defun tcl-do-auto-fill ()
1568   "Auto-fill function for Tcl mode.  Only auto-fills in a comment."
1569   (if (> (current-column) fill-column)
1570       (let ((fill-prefix "# ")
1571             in-comment col)
1572         (save-excursion
1573           (setq in-comment (tcl-in-comment))
1574           (if in-comment
1575               (setq col (1- (current-column)))))
1576         (if in-comment
1577             (progn
1578               (do-auto-fill)
1579               (save-excursion
1580                 (back-to-indentation)
1581                 (delete-region (point) (save-excursion
1582                                          (beginning-of-line)
1583                                          (point)))
1584                 (indent-to-column col)))))))
1585
1586 \f
1587
1588 ;;
1589 ;; Help-related code.
1590 ;;
1591
1592 (defvar tcl-help-saved-dirs nil
1593   "Saved help directories.
1594 If `tcl-help-directory-list' changes, this allows `tcl-help-on-word'
1595 to update the alist.")
1596
1597 (defvar tcl-help-alist nil
1598   "Alist with command names as keys and filenames as values.")
1599
1600 (defun tcl-help-snarf-commands (dirlist)
1601   "Build alist of commands and filenames."
1602   (while dirlist
1603     (let ((files (directory-files (car dirlist) t)))
1604       (while files
1605         (if (and (file-directory-p (car files))
1606                  (not
1607                   (let ((fpart (file-name-nondirectory (car files))))
1608                     (or (equal fpart ".")
1609                         (equal fpart "..")))))
1610             (let ((matches (directory-files (car files) t)))
1611               (while matches
1612                 (or (file-directory-p (car matches))
1613                     (setq tcl-help-alist
1614                           (cons
1615                            (cons (file-name-nondirectory (car matches))
1616                                  (car matches))
1617                            tcl-help-alist)))
1618                 (setq matches (cdr matches)))))
1619         (setq files (cdr files))))
1620     (setq dirlist (cdr dirlist))))
1621
1622 (defun tcl-reread-help-files ()
1623   "Set up to re-read files, and then do it."
1624   (interactive)
1625   (message "Building Tcl help file index...")
1626   (setq tcl-help-saved-dirs tcl-help-directory-list)
1627   (setq tcl-help-alist nil)
1628   (tcl-help-snarf-commands tcl-help-directory-list)
1629   (message "Building Tcl help file index...done"))
1630
1631 (defun tcl-word-no-props ()
1632   "Like `current-word', but strips properties."
1633   (let ((word (current-word)))
1634     (and (fboundp 'set-text-properties)
1635          (set-text-properties 0 (length word) nil word))
1636     word))
1637
1638 (defun tcl-current-word (flag)
1639   "Return current command word, or nil.
1640 If FLAG is nil, just uses `current-word'.
1641 Otherwise scans backward for most likely Tcl command word."
1642   (if (and flag
1643            (memq major-mode '(tcl-mode inferior-tcl-mode)))
1644       (condition-case nil
1645           (save-excursion
1646             ;; Look backward for first word actually in alist.
1647             (if (bobp)
1648                 ()
1649               (while (and (not (bobp))
1650                           (not (tcl-real-command-p)))
1651                 (backward-sexp)))
1652             (if (assoc (tcl-word-no-props) tcl-help-alist)
1653                 (tcl-word-no-props)))
1654         (error nil))
1655     (tcl-word-no-props)))
1656
1657 ;;;###autoload
1658 (defun tcl-help-on-word (command &optional arg)
1659   "Get help on Tcl command.  Default is word at point.
1660 Prefix argument means invert sense of `tcl-use-smart-word-finder'."
1661   (interactive
1662    (list
1663     (progn
1664       (if (not (equal tcl-help-directory-list tcl-help-saved-dirs))
1665           (tcl-reread-help-files))
1666       (let ((word (tcl-current-word
1667                    (if current-prefix-arg
1668                        (not tcl-use-smart-word-finder)
1669                      tcl-use-smart-word-finder))))
1670         (completing-read
1671          (if (or (null word) (string= word ""))
1672              "Help on Tcl command: "
1673            (format "Help on Tcl command (default %s): " word))
1674          tcl-help-alist nil t)))
1675     current-prefix-arg))
1676   (if (not (equal tcl-help-directory-list tcl-help-saved-dirs))
1677       (tcl-reread-help-files))
1678   (if (string= command "")
1679       (setq command (tcl-current-word
1680                      (if arg
1681                          (not tcl-use-smart-word-finder)
1682                        tcl-use-smart-word-finder))))
1683   (let* ((help (get-buffer-create "*Tcl help*"))
1684          (cell (assoc command tcl-help-alist))
1685          (file (and cell (cdr cell))))
1686     (set-buffer help)
1687     (delete-region (point-min) (point-max))
1688     (if file
1689         (progn
1690           (insert "*** " command "\n\n")
1691           (insert-file-contents file))
1692       (if (string= command "")
1693           (insert "Magical Pig!")
1694         (insert "Tcl command " command " not in help\n")))
1695     (set-buffer-modified-p nil)
1696     (goto-char (point-min))
1697     (display-buffer help)))
1698
1699 \f
1700
1701 ;;
1702 ;; Other interactive stuff.
1703 ;;
1704
1705 (defvar tcl-previous-dir/file nil
1706   "Record last directory and file used in loading.
1707 This holds a cons cell of the form `(DIRECTORY . FILE)'
1708 describing the last `tcl-load-file' command.")
1709
1710 (defun tcl-load-file (file &optional and-go)
1711   "Load a Tcl file into the inferior Tcl process.
1712 Prefix argument means switch to the Tcl buffer afterwards."
1713   (interactive
1714    (list
1715     ;; car because comint-get-source returns a list holding the
1716     ;; filename.
1717     (car (comint-get-source "Load Tcl file: "
1718                             (or (and
1719                                  (eq major-mode 'tcl-mode)
1720                                  (buffer-file-name))
1721                                 tcl-previous-dir/file)
1722                             '(tcl-mode) t))
1723     current-prefix-arg))
1724   (comint-check-source file)
1725   (setq tcl-previous-dir/file (cons (file-name-directory file)
1726                                     (file-name-nondirectory file)))
1727   (tcl-send-string (inferior-tcl-proc)
1728                    (format inferior-tcl-source-command (tcl-quote file)))
1729   (if and-go (switch-to-tcl t)))
1730
1731 (defun tcl-restart-with-file (file &optional and-go)
1732   "Restart inferior Tcl with file.
1733 If an inferior Tcl process exists, it is killed first.
1734 Prefix argument means switch to the Tcl buffer afterwards."
1735   (interactive
1736    (list
1737     (car (comint-get-source "Restart with Tcl file: "
1738                             (or (and
1739                                  (eq major-mode 'tcl-mode)
1740                                  (buffer-file-name))
1741                                 tcl-previous-dir/file)
1742                             '(tcl-mode) t))
1743     current-prefix-arg))
1744   (let* ((buf (if (eq major-mode 'inferior-tcl-mode)
1745                   (current-buffer)
1746                 inferior-tcl-buffer))
1747          (proc (and buf (get-process buf))))
1748     (cond
1749      ((not (and buf (get-buffer buf)))
1750       ;; I think this will be ok.
1751       (inferior-tcl tcl-application)
1752       (tcl-load-file file and-go))
1753      ((or
1754        (not (comint-check-proc buf))
1755        (yes-or-no-p
1756         "A Tcl process is running, are you sure you want to reset it? "))
1757       (save-excursion
1758         (comint-check-source file)
1759         (setq tcl-previous-dir/file (cons (file-name-directory file)
1760                                           (file-name-nondirectory file)))
1761         (comint-exec (get-buffer-create buf)
1762                      (if proc
1763                          (process-name proc)
1764                        "inferior-tcl")
1765                      tcl-application file tcl-command-switches)
1766         (if and-go (switch-to-tcl t)))))))
1767
1768 (defun tcl-auto-fill-mode (&optional arg)
1769   "Like `auto-fill-mode', but controls filling of Tcl comments."
1770   (interactive "P")
1771   (and (not tcl-using-emacs-19)
1772        (error "This feature is not supported in Emacs 18"))
1773   ;; Following code taken from "auto-fill-mode" (simple.el).
1774   (prog1
1775       (setq auto-fill-function
1776             (if (if (null arg)
1777                     (not auto-fill-function)
1778                   (> (prefix-numeric-value arg) 0))
1779                 'tcl-do-auto-fill
1780               nil))
1781     (force-mode-line-update)))
1782
1783 ;; hilit19 support from "Chris Alfeld" <calfeld@math.utah.edu>
1784 (defun tcl-hilit ()
1785   (hilit-set-mode-patterns
1786    '(tcl-mode)
1787    '(
1788      ("\\(^ *\\|\; *\\)#.*$" nil comment)
1789      ("[^\\]\\(\\$[A-Za-z0-9\\-\\_./\\(\\)]+\\)" 1 label)
1790      ("[^_]\\<\\(append\\|array\\|auto_execok\\|auto_load\\|auto_mkindex\\|auto_reset\\|break\\|case\\|catch\\|cd\\|close\\|concat\\|continue\\|eof\\|error\\|eval\\|exec\\|exit\\|expr\\|file\\|flush\\|for\\|foreach\\|format\\|gets\\|glob\\|global\\|history\\|if\\|incr\\|info\\|join\\|lappend\\|lindex\\|linsert\\|list\\|llength\\|lrange\\|lreplace\\|lsearch\\|lsort\\|open\\|pid\\|proc\\|puts\\|pwd\\|read\\|regexp\\|regsub\\|rename\\|return\\|scan\\|seek\\|set\\|source\\|split\\|string\\|switch\\|tell\\|time\\|trace\\|unknown\\|unset\\|uplevel\\|upvar\\|while\\)\\>[^_]" 1 keyword) ; tcl keywords
1791      ("[^_]\\<\\(after\\|bell\\|bind\\|bindtags\\|clipboard\\|destroy\\|fileevent\\|focus\\|grab\\|image\\|lower\\|option\\|pack\\|place\\|raise\\|scale\\|selection\\|send\\|subst\\|tk\\|tk_popup\\|tkwait\\|update\\|winfo\\|wm\\)\\>[^_]" 1 define) ; tk keywords
1792      ("[^_]\\<\\(button\\|canvas\\|checkbutton\\|entry\\|frame\\|label\\|listbox\\|menu\\|menubutton\\|message\\|radiobutton\\|scrollbar\\|text\\|toplevel\\)\\>[^_]" 1 decl) ; tk widgets
1793      ("[^_]\\<\\(tix\\((ButtonBox\\|Baloon\\|Control\\|DirList\\|ExFileSelectBox\\|ExFileSelectDialog\\|FileEntry\\|HList\\|LabelEntry\\|LabelFrame\\|NoteBook\\|OptionMenu\\|PanedWindow\\|PopupMenu\\|ScrolledHList\\|ScrolledText\\|ScrolledWindow\\|Select\\|StdButtonBox\\)\\)\\>[^_]" 1 defun) ; tix widgets
1794      ("[{}\\\"\\(\\)]" nil include) ; misc punctuation
1795      )))
1796
1797 (defun tcl-electric-hash (&optional count)
1798   "Insert a `#' and quote if it does not start a real comment.
1799 Prefix arg is number of `#'s to insert.
1800 See variable `tcl-electric-hash-style' for description of quoting
1801 styles."
1802   (interactive "p")
1803   (or count (setq count 1))
1804   (if (> count 0)
1805       (let ((type
1806              (if (eq tcl-electric-hash-style 'smart)
1807                  (if (> count 3)        ; FIXME what is "smart"?
1808                      'quote
1809                    'backslash)
1810                tcl-electric-hash-style))
1811             comment)
1812         (if type
1813             (progn
1814               (save-excursion
1815                 (insert "#")
1816                 (setq comment (tcl-in-comment)))
1817               (delete-char 1)
1818               (and tcl-explain-indentation (message "comment: %s" comment))
1819               (cond
1820                ((eq type 'quote)
1821                 (if (not comment)
1822                     (insert "\"")))
1823                ((eq type 'backslash)
1824                 ;; The following will set count to 0, so the
1825                 ;; insert-char can still be run.
1826                 (if (not comment)
1827                     (while (> count 0)
1828                       (insert "\\#")
1829                       (setq count (1- count)))))
1830                (t nil))))
1831         (insert-char ?# count))))
1832
1833 (defun tcl-hashify-buffer ()
1834   "Quote all `#'s in current buffer that aren't Tcl comments."
1835   (interactive)
1836   (save-excursion
1837     (goto-char (point-min))
1838     (if (and tcl-pps-has-arg-6 tcl-use-hairy-comment-detector)
1839         (let (state
1840               result)
1841           (while (< (point) (point-max))
1842             (setq result (tcl-hairy-scan-for-comment state (point-max) t))
1843             (if (car result)
1844                 (beginning-of-line 2)
1845               (backward-char)
1846               (if (eq ?# (following-char))
1847                   (insert "\\"))
1848               (forward-char))
1849             (setq state (cdr result))))
1850       (while (and (< (point) (point-max))
1851                   (search-forward "#" nil 'move))
1852         (if (tcl-real-comment-p)
1853             (beginning-of-line 2)
1854           ;; There's really no good way for the simple converter to
1855           ;; work.  So we just quote # if it isn't already quoted.
1856           ;; Bogus, but it works.
1857           (backward-char)
1858           (if (not (eq ?\\ (preceding-char)))
1859               (insert "\\"))
1860           (forward-char))))))
1861
1862 (defun tcl-indent-for-comment ()
1863   "Indent this line's comment to comment column, or insert an empty comment.
1864 Is smart about syntax of Tcl comments.
1865 Parts of this were taken from `indent-for-comment'."
1866   (interactive "*")
1867   (end-of-line)
1868   (or (tcl-in-comment)
1869       (progn
1870         ;; Not in a comment, so we have to insert one.  Create an
1871         ;; empty comment (since there isn't one on this line).  If
1872         ;; line is not blank, make sure we insert a ";" first.
1873         (skip-chars-backward " \t")
1874         (let ((eolpoint (point)))
1875           (beginning-of-line)
1876           (if (/= (point) eolpoint)
1877               (progn
1878                 (goto-char eolpoint)
1879                 (insert
1880                  (if (tcl-real-command-p) "" ";")
1881                  "# ")
1882                 (backward-char))))))
1883   ;; Point is just after the "#" starting a comment.  Move it as
1884   ;; appropriate.
1885   (let* ((indent (or (funcall comment-indent-function) comment-column))
1886          (begpos (progn
1887                    (backward-char)
1888                    (point))))
1889     (if (/= begpos indent)
1890         (progn
1891           (skip-chars-backward " \t" (save-excursion
1892                                        (beginning-of-line)
1893                                        (point)))
1894           (delete-region (point) begpos)
1895           (indent-to indent)))
1896     (looking-at comment-start-skip)     ; Always true.
1897     (goto-char (match-end 0))
1898     ;; I don't like the effect of the next two.
1899     ;;(skip-chars-backward " \t" (match-beginning 0))
1900     ;;(skip-chars-backward "^ \t" (match-beginning 0))
1901     ))
1902
1903 ;; The following was inspired by the Tcl editing mode written by
1904 ;; Gregor Schmid <schmid@fb3-s7.math.TU-Berlin.DE>.  His version also
1905 ;; attempts to snarf the command line options from the command line,
1906 ;; but I didn't think that would really be that helpful (doesn't seem
1907 ;; like it owould be right enough.  His version also looks for the
1908 ;; "#!/bin/csh ... exec" hack, but that seemed even less useful.
1909 ;; FIXME should make sure that the application mentioned actually
1910 ;; exists.
1911 (defun tcl-guess-application ()
1912   "Attempt to guess Tcl application by looking at first line.
1913 The first line is assumed to look like \"#!.../program ...\"."
1914   (save-excursion
1915     (goto-char (point-min))
1916     (if (looking-at "#![^ \t]*/\\([^ \t\n/]+\\)\\([ \t]\\|$\\)")
1917         (progn
1918           (make-local-variable 'tcl-application)
1919           (setq tcl-application (buffer-substring (match-beginning 1)
1920                                                   (match-end 1)))))))
1921
1922 ;; This only exists to put on the menubar.  I couldn't figure out any
1923 ;; other way to do it.  FIXME should take "number of #-marks"
1924 ;; argument.
1925 (defun tcl-uncomment-region (beg end)
1926   "Uncomment region."
1927   (interactive "r")
1928   (comment-region beg end -1))
1929
1930 \f
1931
1932 ;;
1933 ;; XEmacs menu support.
1934 ;; Taken from schmid@fb3-s7.math.TU-Berlin.DE (Gregor Schmid),
1935 ;; who wrote a different Tcl mode.
1936 ;; We also have support for menus in Emacs.  We do this by
1937 ;; loading the XEmacs menu emulation code.
1938 ;;
1939
1940 (defun tcl-popup-menu (e)
1941   (interactive "@e")
1942   (and tcl-using-emacs-19
1943        (not tcl-using-xemacs-19)
1944        (if tcl-using-emacs-19-23
1945            (require 'lmenu)
1946          ;; CAVEATS:
1947          ;; * lmenu.el provides 'menubar, which is bogus.
1948          ;; * lmenu.el causes menubars to be turned on everywhere.
1949          ;;   Doubly bogus!
1950          ;; Both of these problems are fixed in Emacs 19.23.  People
1951          ;; using an Emacs before that just suffer.
1952          (require 'menubar "lmenu")))  ;; This is annoying
1953   ;; IMHO popup-menu should be autoloaded.  Oh well.
1954   (popup-menu tcl-xemacs-menu))
1955
1956 \f
1957
1958 ;;
1959 ;; Quoting and unquoting functions.
1960 ;;
1961
1962 ;; This quoting is sufficient to protect eg a filename from any sort
1963 ;; of expansion or splitting.  Tcl quoting sure sucks.
1964 (defun tcl-quote (string)
1965   "Quote STRING according to Tcl rules."
1966   (mapconcat (function (lambda (char)
1967                          (if (memq char '(?[ ?] ?{ ?} ?\\ ?\" ?$ ?  ?\;))
1968                              (concat "\\" (char-to-string char))
1969                            (char-to-string char))))
1970              string ""))
1971
1972 \f
1973
1974 ;;
1975 ;; Bug reporting.
1976 ;;
1977
1978 (and (fboundp 'eval-when-compile)
1979      (eval-when-compile
1980        (require 'reporter)))
1981
1982 (defun tcl-submit-bug-report ()
1983   "Submit via mail a bug report on Tcl mode."
1984   (interactive)
1985   (require 'reporter)
1986   (and
1987    (y-or-n-p "Do you really want to submit a bug report on Tcl mode? ")
1988    (reporter-submit-bug-report
1989     tcl-maintainer
1990     (concat "Tcl mode " tcl-version)
1991     '(tcl-indent-level
1992       tcl-continued-indent-level
1993       tcl-auto-newline
1994       tcl-tab-always-indent
1995       tcl-use-hairy-comment-detector
1996       tcl-electric-hash-style
1997       tcl-help-directory-list
1998       tcl-use-smart-word-finder
1999       tcl-application
2000       tcl-command-switches
2001       tcl-prompt-regexp
2002       inferior-tcl-source-command
2003       tcl-using-emacs-19
2004       tcl-using-emacs-19-23
2005       tcl-using-xemacs-19
2006       tcl-proc-list
2007       tcl-proc-regexp
2008       tcl-typeword-list
2009       tcl-keyword-list
2010       tcl-font-lock-keywords
2011       tcl-pps-has-arg-6))))
2012
2013 \f
2014 ;; XEmacs additions
2015 ;;;###autoload(add-to-list 'auto-mode-alist '("\\.\\(?:tcl\\|exp\\)\\'" . tcl-mode))
2016 ;;;###autoload(add-to-list 'interpreter-mode-alist '("^#!.*\\b\\(scope\\|wish\\|tcl\\|tclsh\\|expect\\)" . tcl-mode))
2017
2018 (provide 'tcl)
2019
2020 ;;; tcl.el ends here