~ubuntu-branches/ubuntu/vivid/inform/vivid

1.1.1 by Jan Christoph Nordholz
Import upstream version 6.31.1+dfsg
1
;;; inform-mode.el --- Inform mode for Emacs
2
3
;; Original-Author: Gareth Rees <Gareth.Rees@cl.cam.ac.uk>
4
;; Maintainer: Rupert Lane <rupert@rupert-lane.org>
5
;; Created: 1 Dec 1994
6
;; Version: 1.5.8
7
;; Released: 3 Sep 2002
8
;; Keywords: languages
9
10
;;; Copyright:
11
12
;; Copyright (c) by Gareth Rees 1996
13
;; Portions copyright (c) by Michael Fessler 1997-1998
14
;; Portions copyright (c) by Rupert Lane 1999-2000, 2002
15
16
;; inform-mode is free software; you can redistribute it and/or modify
17
;; it under the terms of the GNU General Public License as published by
18
;; the Free Software Foundation; either version 2, or (at your option)
19
;; any later version.
20
;;
21
;; inform-mode is distributed in the hope that it will be useful, but
22
;; WITHOUT ANY WARRANTY; without even the implied warranty of
23
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24
;; General Public License for more details.
25
26
;;; Commentary:
27
28
;; Inform is a compiler for adventure games by Graham Nelson,
29
;; available by anonymous FTP from
30
;; /ftp.ifarchive.org:/if-archive/programming/inform/
31
;;
32
;; This file implements a major mode for editing Inform programs.  It
33
;; understands most Inform syntax and is capable of indenting lines
34
;; and formatting quoted strings.  Type `C-h m' within Inform mode for
35
;; more details.
36
;;
37
;; Because Inform header files use the extension ".h" just as C header
38
;; files do, the function `inform-maybe-mode' is provided.  It looks at
39
;; the contents of the current buffer; if it thinks the buffer is in
40
;; Inform, it selects inform-mode; otherwise it selects the mode given
41
;; by the variable `inform-maybe-other'.
42
43
;; Put this file somewhere on your load-path, and the following code in
44
;; your .emacs file:
45
;;
46
;;  (autoload 'inform-mode "inform-mode" "Inform editing mode." t)
47
;;  (autoload 'inform-maybe-mode "inform-mode" "Inform/C header editing mode.")
48
;;  (setq auto-mode-alist
49
;;        (append '(("\\.h\\'"   . inform-maybe-mode)
50
;;                  ("\\.inf\\'" . inform-mode))
51
;;                auto-mode-alist))
52
;;
53
;; To turn on font locking add:
54
;; (add-hook 'inform-mode-hook 'turn-on-font-lock)
55
56
;; If you use XEmacs and intend to use `inform-run-project' with a
57
;; console-mode interpreter, you need to have the eterm package
58
;; installed.  It should already be installed if you use XEmacs < 21,
59
;; but starting with XEmacs 21.1 you may need to download and install
60
;; it separately.
61
62
;; Please send any bugs or comments to rupert@rupert-lane.org
63
64
;;; Code:
65
66
(require 'font-lock)
67
(require 'regexp-opt)
68
69

70
;;;
71
;;; General variables
72
;;;
73
74
(defconst inform-mode-version "1.5.8")
75
76
(defvar inform-maybe-other 'c-mode
77
  "*`inform-maybe-mode' runs this if current file is not in Inform mode.")
78
79
(defvar inform-startup-message t
80
  "*Non-nil means display a message when Inform mode is loaded.")
81
82
(defvar inform-auto-newline t
83
  "*Non-nil means automatically newline before and after braces, and after 
84
semicolons.
85
If you do not want a leading newline before opening braces then use:
86
  \(define-key inform-mode-map \"{\" 'inform-electric-semi\)")
87
88
(defvar inform-mode-map nil
89
  "Keymap for Inform mode.")
90
91
(if inform-mode-map nil
92
  (let ((map (make-sparse-keymap "Inform")))
93
    (setq inform-mode-map (make-sparse-keymap))
94
    (define-key inform-mode-map "\C-m" 'newline-and-indent)
95
    (define-key inform-mode-map "\177" 'backward-delete-char-untabify)
96
    (define-key inform-mode-map "\C-c\C-r" 'inform-retagify)
97
    (define-key inform-mode-map "\C-c\C-t" 'visit-tags-table)
98
    (define-key inform-mode-map "\C-c\C-b" 'inform-build-project)
99
    (define-key inform-mode-map "\C-c\C-c" 'inform-run-project)
100
    (define-key inform-mode-map "\C-c\C-a" 'inform-toggle-auto-newline)
101
    (define-key inform-mode-map "\C-c\C-s" 'inform-spell-check-buffer)
102
    (define-key inform-mode-map "\M-n" 'inform-next-object)
103
    (define-key inform-mode-map "\M-p" 'inform-prev-object)
104
    (define-key inform-mode-map "{" 'inform-electric-brace)
105
    (define-key inform-mode-map "}" 'inform-electric-brace)
106
    (define-key inform-mode-map "]" 'inform-electric-brace)
107
    (define-key inform-mode-map ";" 'inform-electric-semi)
108
    (define-key inform-mode-map ":" 'inform-electric-key)
109
    (define-key inform-mode-map "!" 'inform-electric-key)
110
    (define-key inform-mode-map "," 'inform-electric-comma)
111
    (define-key inform-mode-map [menu-bar] (make-sparse-keymap))
112
    (define-key inform-mode-map [menu-bar inform] (cons "Inform" map))
113
    (define-key map [separator4] '("--" . nil))
114
    (define-key map [inform-spell-check-buffer] 
115
      '("Spellcheck buffer" . inform-spell-check-buffer))
116
    (define-key map [ispell-region] '("Spellcheck region" . ispell-region))
117
    (define-key map [ispell-word] '("Spellcheck word" . ispell-word))
118
    (define-key map [separator3] '("--" . nil))
119
    (define-key map [load-tags] '("Load tags table" . visit-tags-table))
120
    (define-key map [retagify] '("Rebuild tags table" . inform-retagify))
121
    (define-key map [build] '("Build project" . inform-build-project))
122
    (define-key map [run] '("Run project" . inform-run-project))
123
    (define-key map [separator2] '("--" . nil))
124
    (define-key map [next-object] '("Next object" . inform-next-object))
125
    (define-key map [prev-object] '("Previous object" . inform-prev-object))
126
    (define-key map [separator1] '("--" . nil))
127
    (define-key map [comment-region] '("Comment Out Region" . comment-region))
128
    (put 'comment-region 'menu-enable 'mark-active)
129
    (define-key map [indent-region] '("Indent Region" . indent-region))
130
    (put 'indent-region 'menu-enable 'mark-active)
131
    (define-key map [indent-line] '("Indent Line" . indent-for-tab-command))))
132
133
(defvar inform-mode-abbrev-table nil
134
  "Abbrev table used while in Inform mode.")
135
136
(define-abbrev-table 'inform-mode-abbrev-table nil)
137
138
(defvar inform-project-file nil
139
  "*The top-level Inform project file to which the current file belongs.")
140
(make-variable-buffer-local 'inform-project-file)
141
142
(defvar inform-autoload-tags t
143
  "*Non-nil means automatically load tags table when entering Inform mode.")
144
145
(defvar inform-etags-program "etags"
146
  "The shell command with which to run the etags program.")
147
148
(defvar inform-command "inform"
149
  "*The shell command with which to run the Inform compiler.")
150
151
(defvar inform-libraries-directory nil
152
  "*If non-NIL, gives the directory in which libraries are found.")
153
154
(defvar inform-command-options ""
155
  "*Options with which to call the Inform compiler.")
156
157
(defvar inform-interpreter-command "frotz"
158
  "*The command with which to run the ZCode interpreter.
159
If a string, the name of a command.  If a symbol or a function value, an
160
Emacs-lisp function to be called with the name of the story file.")
161
162
(defvar inform-interpreter-options ""
163
  "*Additional options with which to call the ZCode interpreter.
164
Only used if `inform-interpreter-command' is a string.")
165
166
(defvar inform-interpreter-kill-old-process t
167
  "*Whether to kill the old interpreter process when starting a new one.")
168
169
(defvar inform-interpreter-is-graphical nil
170
  "*Controls whether `inform-interpreter-command' will be run in a buffer.
171
If NIL, `inform-run-project' will switch to the interpreter buffer after
172
running the interpreter.")
173
174
175

176
;;;
177
;;; Indentation parameters
178
;;;
179
180
(defvar inform-indent-property 8
181
  "*Indentation of the start of a property declaration.")
182
183
(defvar inform-indent-has-with-class 1
184
  "*Indentation of has/with/class lines in object declarations.")
185
186
(defvar inform-indent-level 4
187
  "*Indentation of lines of block relative to first line of block.")
188
189
(defvar inform-indent-label-offset -3
190
  "*Indentation of label relative to where it should be.")
191
192
(defvar inform-indent-cont-statement 4
193
  "*Indentation of continuation relative to start of statement.")
194
195
(defvar inform-indent-fixup-space t
196
  "*If non-NIL, fix up space in object declarations.")
197
198
(defvar inform-indent-action-column 40
199
  "*Column at which action names should be placed in verb declarations.")
200
201
(defvar inform-comments-line-up-p nil
202
  "*If non-nil, comments spread over several lines will line up with the first.")
203
204
(defvar inform-strings-line-up-p nil
205
  "*Variable controlling indentation of multi-line strings.
206
If nil (default), string will be indented according to context.
207
If a number, will always set the indentation to that column.
208
If 'char', will line up with the first character of the string.
209
If 'quote', or other non-nil value, will line up with open quote on 
210
first line.")
211

212
;;;
213
;;; Syntax variables
214
;;;
215
216
(defvar inform-mode-syntax-table nil
217
  "Syntax table to use in Inform mode buffers.")
218
219
(if inform-mode-syntax-table
220
    nil
221
  (setq inform-mode-syntax-table (make-syntax-table))
222
  (modify-syntax-entry ?\\ "\\" inform-mode-syntax-table)
223
  (modify-syntax-entry ?\n ">" inform-mode-syntax-table)
224
  (modify-syntax-entry ?! "<" inform-mode-syntax-table)
225
  (modify-syntax-entry ?# "_" inform-mode-syntax-table)
226
  (modify-syntax-entry ?% "." inform-mode-syntax-table)
227
  (modify-syntax-entry ?& "." inform-mode-syntax-table)
228
  (modify-syntax-entry ?\' "." inform-mode-syntax-table)
229
  (modify-syntax-entry ?* "." inform-mode-syntax-table)
230
  (modify-syntax-entry ?- "." inform-mode-syntax-table)
231
  (modify-syntax-entry ?/ "." inform-mode-syntax-table)
232
  (modify-syntax-entry ?\; "." inform-mode-syntax-table)
233
  (modify-syntax-entry ?< "." inform-mode-syntax-table)
234
  (modify-syntax-entry ?= "." inform-mode-syntax-table)
235
  (modify-syntax-entry ?> "." inform-mode-syntax-table)
236
  (modify-syntax-entry ?+ "." inform-mode-syntax-table)
237
  (modify-syntax-entry ?| "." inform-mode-syntax-table)
238
  (modify-syntax-entry ?^ "w" inform-mode-syntax-table))
239
240

241
;;; Keyword definitions-------------------------------------------------------
242
243
;; These are used for syntax and font-lock purposes.
244
;; They combine words used in Inform 5 and Inform 6 for full compatability.
245
;; You can add new keywords directly to this list as the regexps for
246
;; font-locking are defined when this file is byte-compiled or eval'd.
247
248
(eval-and-compile
249
  (defvar inform-directive-list
250
    '("abbreviate" "array" "attribute" "btrace" "class" "constant"
251
      "default" "dictionary" "end" "endif" "etrace" "extend" "fake_action"
252
      "global" "ifdef" "ifndef" "iftrue" "iffalse" "ifv3" "ifv5" "import"
253
      "include" "link" "listsymbols" "listdict" "listverbs" "lowstring"
254
      "ltrace" "message" "nearby" "nobtrace" "noetrace" "noltrace" "notrace"
255
      "object" "property" "release" "replace" "serial" "statusline" "stub"
256
      "switches" "system_file" "trace" "verb" "zcharacter")
257
    "List of Inform directives that shouldn't appear embedded in code.")
258
259
  (defvar inform-defining-list
260
    '("[" "array" "attribute" "class" "constant" "fake_action" "global"
261
      "lowstring" "nearby" "object" "property")
262
    "List of Inform directives that define a variable/constant name.
263
Used to build a font-lock regexp; the name defined must follow the
264
keyword.")
265
266
  ;; We have to hardcode the regexp for inform-defining-list due to the way
267
  ;; regexp-opt works on different emacsen.
268
  ;; On Emacs 20 it always uses regular \( \) grouping
269
  ;; On Emacs 21 it always uses shy \(?: \) grouping
270
  ;; On XEmacs it can use either based on the shy parameter.
271
  ;; This means it is impossible to write a match-string expression in
272
  ;; inform-font-lock-keywords using regexp-opt that will work on all emacsen.
273
  ;; If Emacs 20 support is dropped this should be removed and shy grouping
274
  ;; used.
275
  (defvar inform-defining-list-regexp
276
    "\\[\\|a\\(rray\\|ttribute\\)\\|c\\(lass\\|onstant\\)\\|fake_action\\|global\\|lowstring\\|nearby\\|object\\|property"
277
    "Regexp based on inform-defining-list, hardcoded for portability.")
278
279
  (defvar inform-attribute-list
280
    '("absent" "animate" "clothing" "concealed" "container" "door"
281
      "edible" "enterable" "female" "general" "light" "lockable" "locked"
282
      "male" "moved" "neuter" "on" "open" "openable" "pluralname" "proper"
283
      "scenery" "scored" "static" "supporter" "switchable" "talkable"
284
      "transparent" "visited" "workflag" "worn")
285
    "List of Inform attributes defined in the library.")
286
287
  (defvar inform-property-list
288
    '("n_to" "s_to" "e_to" "w_to" "ne_to" "se_to" "nw_to" "sw_to" "u_to"
289
      "d_to" "in_to" "out_to" "add_to_scope" "after" "article" "articles"
290
      "before" "cant_go" "capacity" "daemon" "describe" "description"
291
      "door_dir" "door_to" "each_turn" "found_in" "grammar" "initial"
292
      "inside_description" "invent" "life" "list_together" "name" "number"
293
      "orders" "parse_name" "plural" "react_after" "react_before"
294
      "short_name" "time_left" "time_out" "when_closed" "when_open"
295
      "when_on" "when_off" "with_key")
296
    "List of Inform properties defined in the library.")
297
298
  (defvar inform-code-keyword-list
299
    '("box" "break" "continue" "do" "else" "font off" "font on" "for"
300
      "give" "has" "hasnt" "if" "inversion" "jump" "move" "new_line" "notin"
301
      "objectloop" "ofclass" "print" "print_ret" "quit" "read" "remove"
302
      "restore" "return" "rfalse" "rtrue" "save" "spaces" "string"
303
      "style bold" "style fixed" "style reverse" "style roman" "style underline"
304
      "switch" "to" "until" "while")
305
    "List of Inform code keywords.")
306
  )
307
308
;; Some regular expressions are needed at compile-time too so as to
309
;; avoid postponing the work to load time.
310
311
;; To do the work of building the regexps we use regexp-opt, which has
312
;; different behaviour on XEmacs and GNU Emacs and may not even be
313
;; available on ancient versions
314
(eval-and-compile
315
  (defun inform-make-regexp (strings &optional paren shy)
316
    (cond 
317
     ((string-match "XEmacs\\|Lucid" emacs-version)
318
      ;; XEmacs
319
      (regexp-opt strings paren shy))
320
     (t
321
      ;; GNU Emacs
322
      (regexp-opt strings)))))
323
324
(eval-and-compile
325
  (defvar inform-directive-regexp
326
    (concat "\\<#?\\("
327
            (inform-make-regexp inform-directive-list)
328
            "\\)\\>")
329
    "Regular expression matching an Inform directive.")
330
331
  (defvar inform-object-regexp
332
    "#?\\<\\(object\\|nearby\\|class\\)\\>"
333
    "Regular expression matching start of object declaration.")
334
335
  (defvar inform-property-regexp
336
    (concat "\\s-*\\("
337
            (inform-make-regexp inform-property-list)
338
            "\\)")
339
    "Regular expression matching Inform properties."))
340
341
342
(defvar inform-real-object-regexp
343
  (eval-when-compile (concat "^" inform-object-regexp))
344
  "Regular expression matching the start of a real object declaration.
345
That is, one found at the start of a line.")
346
347
(defvar inform-label-regexp "[^]:\"!\(\n]+\\(:\\|,\\)"
348
  "Regular expression matching a label.")
349
350
(defvar inform-action-regexp "\\s-*\\*"
351
  "Regular expression matching an action line in a verb declaration.")
352
353
(defvar inform-statement-terminators '(?\; ?{ ?} ?: ?\) do else)
354
  "Tokens which precede the beginning of a statement.")
355
356

357
;;;
358
;;; Font-lock keywords
359
;;;
360
361
(defvar inform-font-lock-defaults
362
  '(inform-font-lock-keywords nil t ((?_ . "w") (?' . "$")) inform-prev-object)
363
  "Font Lock defaults for Inform mode.")
364
365
(defface inform-dictionary-word-face
366
  '((((class color) (background light)) (:foreground "Red"))
367
    (((class color) (background dark)) (:foreground "Pink"))
368
    (t (:italic t :bold t)))
369
  "Font lock mode face used to highlight dictionary words.")
370
371
(defvar inform-dictionary-word-face 'inform-dictionary-word-face
372
  "Variable for Font lock mode face used to highlight dictionary words.")
373
374
(defvar inform-font-lock-keywords
375
  (eval-when-compile
376
    (list
377
378
     ;; Inform code keywords
379
     (cons (concat "\\s-\\("
380
                   (inform-make-regexp inform-code-keyword-list)
381
                   "\\)\\(\\s-\\|$\\|;\\)")
382
           '(1 font-lock-keyword-face))
383
     
384
     ;; Keywords that declare variable or constant names.
385
     (list 
386
      (concat "^#?\\("
387
              inform-defining-list-regexp
388
              "\\)\\s-+\\(->\\s-+\\)*\\(\\(\\w\\|\\s_\\)+\\)")
389
      '(1 font-lock-keyword-face)
390
      '(5 font-lock-function-name-face))
391
392
     ;; Other directives.
393
     (cons inform-directive-regexp 'font-lock-keyword-face)
394
395
     ;; Single quoted strings, length > 1, are dictionary words
396
     '("'\\(\\(-\\|\\w\\)\\(\\(-\\|\\w\\)+\\(//\\w*\\)?\\|//\\w*\\)\\)'"
397
       (1 inform-dictionary-word-face append))
398
399
     ;; Double-quoted dictionary words
400
     '("\\(\\s-name\\s-\\|^Verb\\|^Extend\\|^\\s-+\\*\\)"
401
       ("\"\\(\\(-\\|\\w\\)+\\)\"" nil nil
402
        (1 inform-dictionary-word-face t)))
403
404
     ;; More double-quoted dictionary words
405
     '("^\\s-+\"\\(\\(-\\|\\w\\)+\\)\"\\s-+\"\\(\\(-\\|\\w\\)+\\)\""
406
       (1 inform-dictionary-word-face t)
407
       (3 inform-dictionary-word-face t)
408
       ("\"\\(\\(-\\|\\w\\)+\\)\"" nil nil
409
        (1 inform-dictionary-word-face t)))
410
411
     ;; `private', `class', `has' and `with' in objects.
412
     '("^\\s-+\\(private\\|class\\|has\\|with\\)\\(\\s-\\|$\\)"
413
       (1 font-lock-keyword-face))
414
415
     ;; Attributes and properties.
416
     (cons (concat "[^#]\\<\\("
417
                   (inform-make-regexp (append inform-attribute-list
418
                                               inform-property-list))
419
                   "\\)\\>")
420
           '(1 font-lock-variable-name-face))))
421
  "Expressions to fontify in Inform mode.")
422
423

424
;;;
425
;;; Inform mode
426
;;;
427
428
(defun inform-mode ()
429
  "Major mode for editing Inform programs.
430
431
* Inform syntax:
432
433
  Type \\[indent-for-tab-command] to indent the current line.
434
  Type \\[indent-region] to indent the region.
435
436
  Type \\[fill-paragraph] to fill strings or comments.
437
  This compresses multiple spaces into single spaces.
438
439
* Multi-file projects:
440
441
  The variable `inform-project-file' gives the name of the root file of
442
  the project \(i.e., the one that you run Inform on\)\; it is best to
443
  set this as a local variable in each file, for example by making
444
     ! -*- inform-project-file:\"game.inf\" -*-
445
  the first line of the file.
446
447
* Tags tables:
448
449
  Type \\[inform-retagify] to build \(and load\) a Tags table.
450
  Type \\[visit-tags-table] to load an existing Tags table.
451
  If it exists, and if the variable `inform-autoload-tags' is non-NIL,
452
  the Tags table is loaded on entry to Inform Mode.
453
  With a Tags table loaded, type \\[find-tag] to find the declaration of
454
  the object, class or function under point.
455
456
* Navigating in a file:
457
458
  Type \\[inform-prev-object] to go to the previous object/class declaration.
459
  Type \\[inform-next-object] to go to the next one.
460
461
* Compilation:
462
463
  Type \\[inform-build-project] to build the current project.
464
  Type \\[next-error] to go to the next error.
465
466
* Running:
467
468
  Type \\[inform-run-project] to run the current project in an
469
  interpreter, either as a sepaarte process or in an Emacs terminal buffer.
470
471
* Font-lock support:
472
473
  Put \(add-hook 'inform-mode-hook 'turn-on-font-lock) in your .emacs.
474
475
* Spell checking:
476
477
  Type \\[inform-spell-check-buffer] to spell check all strings in the buffer.
478
  Type \\[ispell-word] to check the single word at point.
479
480
* Key definitions:
481
482
\\{inform-mode-map}
483
* Functions:
484
485
  inform-maybe-mode
486
    Looks at the contents of a file, guesses whether it is an Inform
487
    program, runs `inform-mode' if so, or `inform-maybe-other' if not.
488
    The latter defaults to `c-mode'.  Used for header files which might
489
    be Inform or C programs.
490
491
* Miscellaneous user options:
492
493
  inform-startup-message
494
    Set to nil to inhibit message first time Inform mode is used.
495
496
  inform-maybe-other
497
    The mode used by `inform-maybe-mode' if it guesses that the file is
498
    not an Inform program.
499
500
  inform-mode-hook
501
    This hook is run after entry to Inform Mode.
502
503
  inform-autoload-tags
504
    If non-nil, then a tags table will automatically be loaded when
505
    entering Inform mode.
506
507
  inform-auto-newline
508
    If non-nil, then newlines are automatically inserted before and
509
    after braces, and after semicolons in Inform code, and after commas
510
    in object declarations.
511
512
* User options controlling indentation style:
513
514
  Values in parentheses are the default indentation style.
515
516
  inform-indent-property \(8\)
517
    Indentation of a property or attribute in an object declaration.
518
519
  inform-indent-has-with-class \(1\)
520
    Indentation of has/with/class/private lines in object declaration.
521
522
  inform-indent-level \(4\)
523
    Indentation of line of code in a block relative to the first line of
524
    the block.
525
526
  inform-indent-label-offset \(-3\)
527
    Indentation of a line starting with a label, relative to the
528
    indentation if the label were absent.
529
530
  inform-indent-cont-statement \(4\)
531
    Indentation of second and subsequent lines of a statement, relative
532
    to the first.
533
534
  inform-indent-fixup-space \(T\)
535
    If non-NIL, fix up space after `Object', `Class', `Nearby', `has',
536
    `private' and `with', so that all the object's properties line up.
537
538
  inform-indent-action-column \(40\)
539
    Column at which action names should be placed in verb declarations.
540
    If NIL, then action names are not moved.
541
542
  inform-comments-line-up-p \(NIL\)
543
    If non-NIL, comments spread out over several lines will start on the
544
    same column as the first comment line.
545
546
  inform-strings-line-up-p \(NIL\)
547
    Variable controlling indentation of multi-line strings.
548
    If nil (default), string will be indented according to context.
549
    If a number, will always set the indentation to that column.
550
    If 'char', will line up with the first character of the string.
551
    If 'quote', or other non-nil value, will line up with open quote on 
552
    first line.
553
554
* User options to do with compilation:
555
556
  inform-command
557
    The shell command with which to run the Inform compiler.
558
559
  inform-libraries-directory
560
    If non-NIL, gives the directory in which the Inform libraries are
561
    found.
562
563
  inform-command-options
564
    Additional options with which to call the Inform compiler.
565
566
* User options to do with an interpreter:
567
568
  inform-interpreter-command
569
    The command with which to run the ZCode interpreter.  Can be a
570
    string (a command to be run), a symbol (name of function to call)
571
    or a function.
572
573
  inform-interpreter-options
574
    Additional options with which to call the ZCode interpreter.  Only
575
    used if `inform-interpreter-command' is a string.
576
577
  inform-interpreter-kill-old-process
578
    If non-NIL, `inform-run-project' will kill any running interpreter
579
    process and start a new one.  If not, will switch to the interpreter's
580
    buffer (if necessary - see documentation for `inform-run-project' for
581
    details).
582
583
  inform-interpreter-is-graphical
584
    If NIL, `inform-run-project' will switch to the interpreter buffer
585
    after running the interpreter.
586
587
588
* Please send any bugs or comments to rupert@rupert-lane.org
589
"
590
591
  (interactive)
592
  (if inform-startup-message
593
      (message "Emacs Inform mode version %s." inform-mode-version))
594
  (kill-all-local-variables)
595
  (use-local-map inform-mode-map)
596
  (set-syntax-table inform-mode-syntax-table)
597
  (make-local-variable 'comment-column)
598
  (make-local-variable 'comment-end)
599
  (make-local-variable 'comment-indent-function)
600
  (make-local-variable 'comment-start)
601
  (make-local-variable 'comment-start-skip)
602
  (make-local-variable 'fill-paragraph-function)
603
  (make-local-variable 'font-lock-defaults)
604
  (make-local-variable 'imenu-extract-index-name-function)
605
  (make-local-variable 'imenu-prev-index-position-function)
606
  (make-local-variable 'indent-line-function)
607
  (make-local-variable 'indent-region-function)
608
  (make-local-variable 'parse-sexp-ignore-comments)
609
  (make-local-variable 'require-final-newline)
610
  (setq comment-column 40
611
        comment-end ""
612
        comment-indent-function 'inform-comment-indent
613
        comment-start "!"
614
        comment-start-skip "!+\\s-*"
615
        fill-paragraph-function 'inform-fill-paragraph
616
        font-lock-defaults inform-font-lock-defaults
617
        imenu-extract-index-name-function 'inform-imenu-extract-name
618
        imenu-prev-index-position-function 'inform-prev-object
619
        indent-line-function 'inform-indent-line
620
        indent-region-function 'inform-indent-region
621
        inform-startup-message nil
622
        local-abbrev-table inform-mode-abbrev-table
623
        major-mode 'inform-mode
624
        mode-name "Inform"
625
        parse-sexp-ignore-comments t
626
        require-final-newline t)
627
  (auto-fill-mode 1)
628
  (if inform-autoload-tags
629
      (inform-auto-load-tags-table))
630
  (run-hooks 'inform-mode-hook))
631
632
(defun inform-maybe-mode ()
633
  "Starts Inform mode if file is in Inform; `inform-maybe-other' otherwise."
634
  (let ((case-fold-search t))
635
    (if (save-excursion
636
          (re-search-forward
637
           "^\\(!\\|object\\|nearby\\|\\[ \\)"
638
           nil t))
639
        (inform-mode)
640
      (funcall inform-maybe-other))))
641
642

643
;;;
644
;;; Syntax and indentation
645
;;;
646
647
;; Go to the start of the current Inform definition.  Just goes to the
648
;; most recent line with a function beginning [, or a directive.
649
650
(defun inform-beginning-of-defun ()
651
  (let ((case-fold-search t))
652
    (catch 'found
653
      (end-of-line 1)
654
      (while (re-search-backward "\n[[a-z]" nil 'move)
655
        (forward-char 1)
656
        (if (or (and (looking-at "\\[")
657
                     (eq (inform-preceding-char) ?\;))
658
                (looking-at inform-directive-regexp))
659
            (throw 'found nil))
660
        (forward-char -1)))))
661
662
;; Returns preceding non-blank, non-comment character in buffer.  It is
663
;; assumed that point is not inside a string or comment.
664
665
(defun inform-preceding-char ()
666
  (save-excursion
667
    (while (/= (point) (progn (forward-comment -1) (point))))
668
    (skip-syntax-backward " ")
669
    (if (bobp) ?\;
670
      (preceding-char))))
671
672
;; Returns preceding non-blank, non-comment token in buffer, either the
673
;; character itself, or the tokens 'do or 'else.  It is assumed that
674
;; point is not inside a string or comment.
675
676
(defun inform-preceding-token ()
677
  (save-excursion
678
    (while (/= (point) (progn (forward-comment -1) (point))))
679
    (skip-syntax-backward " ")
680
    (if (bobp) ?\;
681
      (let ((p (preceding-char)))
682
        (cond ((and (eq p ?o)
683
                    (>= (- (point) 2) (point-min)))
684
               (goto-char (- (point) 2))
685
               (if (looking-at "\\<do") 'do p))
686
              ((and (eq p ?e)
687
                    (>= (- (point) 4) (point-min)))
688
               (goto-char (- (point) 4))
689
               (if (looking-at "\\<else") 'else p))
690
              (t p))))))
691
692
;; `inform-syntax-class' returns a list describing the syntax at point.
693
694
;; Optional argument DEFUN-START gives the point from which parsing
695
;; should start, and DATA is the list returned by a previous invocation
696
;; of `inform-syntax-class'.
697
698
;; The returned list is of the form (SYNTAX IN-OBJ SEXPS STATE).
699
;; SYNTAX is one of
700
701
;;  directive  An Inform directive (given by `inform-directive-list')
702
;;  has        The "has" keyword
703
;;  with       The "with" keyword
704
;;  class      The "class" keyword
705
;;  private    The "private" keyword
706
;;  property   A property or attribute
707
;;  other      Any other line not in a function body
708
;;  string     The line begins inside a string
709
;;  comment    The line starts with a comment
710
;;  label      Line contains a label (i.e. has a colon in it)
711
;;  code       Any other line inside a function body
712
;;  blank      A blank line
713
;;  action     An action line in a verb declaration
714
715
;; IN-OBJ is non-NIL if the line appears to be inside an Object, Nearby,
716
;; or Class declaration.
717
718
;; SEXPS is a list of pairs (D . P) where P is the start of a sexp
719
;; containing point and D is its nesting depth.  The pairs are in
720
;; descreasing order of nesting depth.
721
722
;; STATE is the list returned by `parse-partial-sexp'.
723
724
;; For reasons of speed, `inform-syntax-class' looks for directives only
725
;; at the start of lines.  If the source contains top-level directives
726
;; not at the start of lines, or anything else at the start of a line
727
;; that might be mistaken for a directive, the wrong syntax class may be
728
;; returned.
729
730
;; There are circumstances in which SEXPS might not be complete (namely
731
;; if there were multiple opening brackets and some but not all have
732
;; been closed since the last call to `inform-syntax-class'), and rare
733
;; circumstances in which it might be wrong (namely if there are
734
;; multiple closing brackets and fewer, but at least two, opening
735
;; bracket since the last call).  I consider these cases not worth
736
;; worrying about - and the speed hit of checking for them is
737
;; considerable.
738
739
(defun inform-syntax-class (&optional defun-start data)
740
  (let ((line-start (point))
741
        in-obj state
742
        (case-fold-search t))
743
    (save-excursion
744
      (cond (defun-start
745
              (setq state (parse-partial-sexp defun-start line-start nil nil
746
                                              (nth 3 data)))
747
              (setq in-obj
748
                    (cond ((or (> (car state) 0) (nth 3 state) (nth 4 state))
749
                           (nth 1 data))
750
                          ((nth 1 data) (/= (inform-preceding-char) ?\;))
751
                          (t (looking-at inform-object-regexp)))))
752
            (t
753
             (inform-beginning-of-defun)
754
             (setq in-obj (looking-at inform-object-regexp)
755
                   state (parse-partial-sexp (point) line-start)))))
756
757
    (list
758
     (if (> (car state) 0)
759
         ;; If there's a containing sexp then it's easy.
760
         (cond ((nth 3 state) 'string)
761
               ((nth 4 state) 'comment)
762
               ((looking-at (concat "\\s-*" comment-start)) 'comment)
763
               ((looking-at inform-label-regexp) 'label)
764
               (t 'code))
765
766
       ;; Otherwise there are a bunch of special cases (has, with, class,
767
       ;; and private properties) that must be checked for.  Note that
768
       ;; we have to distinguish between global class declarations and
769
       ;; class membership in an object declaration.  This is done by
770
       ;; looking for a preceding semicolon.
771
       (cond ((nth 3 state) 'string)
772
             ((nth 4 state) 'comment)
773
             ((looking-at (concat "\\s-*" comment-start)) 'comment)
774
             ((and in-obj (looking-at "\\s-*class\\>")
775
                   (/= (inform-preceding-char) ?\;))
776
              'class)
777
             ((looking-at inform-action-regexp) 'action)
778
             ((looking-at inform-directive-regexp) 'directive)
779
             ((and (looking-at "\\[") (eq (inform-preceding-char) ?\;))
780
              'directive)
781
             ((and (not in-obj) (eq (inform-preceding-char) ?\;))
782
              'directive)
783
             ((looking-at "\\s-*$") 'blank)
784
             ((not in-obj) 'other)
785
             ((looking-at "\\s-*has\\(\\s-\\|$\\)") 'has)
786
             ((looking-at "\\s-*with\\(\\s-\\|$\\)") 'with)
787
             ((looking-at "\\s-*private\\(\\s-\\|$\\)") 'private)
788
             ((or (eq (inform-preceding-char) ?,)
789
                  (looking-at inform-property-regexp))
790
              'property)
791
             ;; This handles declarations of objects in a class eg
792
             ;; Bird "swallow";
793
             ;; It assumes that class names follow the convention of being
794
             ;; capitalised. This is not the most elegent way of handling
795
             ;; this case but in practice works well.
796
             ((looking-at "\\s-*[A-Z]")
797
              'directive)
798
             (t 
799
              'other)))
800
801
     ;; Are we in an object?
802
     (if (and in-obj
803
              (not (looking-at inform-object-regexp))
804
              (zerop (car state))
805
              (eq (inform-preceding-char) ?\;))
806
         nil
807
       in-obj)
808
809
     ;; List of known enclosing sexps.
810
     (let ((sexps (nth 2 data))         ; the old list of sexps
811
           (depth (car state))          ; current nesting depth
812
           (sexp-start (nth 1 state)))  ; enclosing sexp, if any
813
       (if sexps
814
           ;; Strip away closed sexps.
815
           (let ((sexp-depth (car (car sexps))))
816
             (while (and sexps (or (> sexp-depth depth)
817
                                   (and (eq sexp-depth depth)
818
                                        sexp-start)))
819
               (setq sexps (cdr sexps)
820
                     sexp-depth (if sexps (car (car sexps)))))))
821
       (if sexp-start
822
           (setq sexps (cons (cons depth sexp-start) sexps)))
823
       sexps)
824
825
     ;; State from the parse algorithm.
826
     state)))
827
828
;; Returns the correct indentation for the line at point.  DATA is the
829
;; syntax class for the start of the line (as returned by
830
;; `inform-syntax-class').  It is assumed that point is somewhere in the
831
;; indentation for the current line (i.e., everything to the left is
832
;; whitespace).
833
834
(defun inform-calculate-indentation (data)
835
  (let ((syntax (car data))             ; syntax class of start of line
836
        (in-obj (nth 1 data))           ; inside an object?
837
        (depth (car (nth 3 data)))      ; depth of nesting of start of line
838
        (case-fold-search t))           ; searches are case-insensitive
839
    (cond
840
841
     ;; Directives should never be indented or else the directive-
842
     ;; finding code won't run fast enough.  Hence the magic
843
     ;; constant 0.
844
     ((eq syntax 'directive) 0)
845
     ((eq syntax 'blank) 0)
846
847
     ;; Various standard indentations.
848
     ((eq syntax 'property) inform-indent-property)
849
     ((eq syntax 'other)
850
      (cond ((looking-at "\\s-*\\[") inform-indent-property)
851
            (in-obj (+ inform-indent-property inform-indent-level))
852
            (t inform-indent-level)))
853
     ((and (eq syntax 'string) (zerop depth))
854
      (cond (in-obj (+ inform-indent-property inform-indent-level))
855
            (t inform-indent-level)))
856
     ((and (eq syntax 'comment) (zerop depth))
857
      (inform-line-up-comment
858
       (if in-obj inform-indent-property 0)))
859
     ((eq syntax 'action) inform-indent-level)
860
     ((memq syntax '(has with class private)) inform-indent-has-with-class)
861
862
     ;; We are inside a sexp of some sort.
863
     (t
864
      (let ((indent 0)                  ; calculated indent column
865
            paren                       ; where the enclosing sexp begins
866
            string-start                ; where string (if any) starts
867
            (string-indent 0)           ; indentation for the current str
868
            cont-p                      ; true if line is a continuation
869
            paren-char                  ; the parenthesis character
870
            prec-token                  ; token preceding line
871
            this-char)                  ; character under consideration
872
        (save-excursion
873
874
          ;; Skip back to the start of a string, if any.  (Note that
875
          ;; we can't be in a comment since the syntax class applies
876
          ;; to the start of the line.)
877
          (if (eq syntax 'string)
878
              (progn
879
                (skip-syntax-backward "^\"")
880
                (forward-char -1)
881
                (setq string-start (point))
882
                (setq string-indent (current-column))
883
                ))
884
885
          ;; Now find the start of the sexp containing point.  Most
886
          ;; likely, the location was found by `inform-syntax-class';
887
          ;; if not, call `up-list' now and save the result in case
888
          ;; it's useful in future.
889
          (save-excursion
890
            (let ((sexps (nth 2 data)))
891
              (if (and sexps (eq (car (car sexps)) depth))
892
                  (goto-char (cdr (car sexps)))
893
                (up-list -1)
894
                (setcar (nthcdr 2 data)
895
                        (cons (cons depth (point)) (nth 2 data)))))
896
            (setq paren (point)
897
                  paren-char (following-char)))
898
899
          ;; If we were in a string, now skip back to the start of the
900
          ;; line.  We have to do this *after* calling `up-list' just
901
          ;; in case there was an opening parenthesis on the line
902
          ;; including the start of the string.
903
          (if (eq syntax 'string)
904
              (forward-line 0))
905
906
          ;; The indentation depends on what kind of sexp we are in.
907
          ;; If line is in parentheses, indent to opening parenthesis.
908
          (if (eq paren-char ?\()
909
              (setq indent (progn (goto-char paren) (1+ (current-column))))
910
        
911
            ;; Line not in parentheses.
912
            (setq prec-token (inform-preceding-token)
913
                  this-char (following-char))
914
            (cond
915
916
             ;; Each 'else' should have the same indentation as the 
917
             ;; matching 'if'
918
             ((looking-at "\\s-*else")
919
              ;; Find the matching 'if' by counting 'if's and 'else's 
920
              ;; in this sexp
921
              (let ((if-count 0) found)
922
                (while (and (not found)
923
                            (progn (forward-sexp -1) t) ; skip over sub-sexps
924
                            (re-search-backward "\\s-*\\(else\\|if\\)" 
925
                                                paren t))
926
                  (setq if-count (+ if-count
927
                                    (if (string= (match-string 1) "else")
928
                                        -1 1)))
929
                  (if (eq if-count 1) (setq found t)))
930
                (if (not found)
931
                    (setq indent 0)
932
                  (forward-line 0)
933
                  (skip-syntax-forward " ")
934
                  (setq indent (current-column)))))
935
936
             ;; Line is an inlined directive-- always put on column 0
937
             ((looking-at "\\s-*#[^#]")
938
              (setq indent 0))
939
             
940
             ;; Line is in an implicit block: take indentation from
941
             ;; the line that introduces the block, plus one level.
942
             ((memq prec-token '(?\) do else))
943
              (forward-sexp -1)
944
              (forward-line 0)
945
              (skip-syntax-forward " ")
946
              (setq indent
947
                    (+ (current-column)
948
                       (if (eq this-char ?{) 0 inform-indent-level))))
949
950
             ;; Line is a continued statement.
951
             ((not (or (memq prec-token inform-statement-terminators)
952
                       (eq syntax 'label)))
953
              (setq cont-p t)
954
              (forward-line -1)
955
              (let ((token (inform-preceding-token)))
956
                ;; Is it the first continuation line?
957
                (if (memq token inform-statement-terminators)
958
                    (setq indent inform-indent-cont-statement)))
959
              (skip-syntax-forward " ")
960
              (setq indent (+ indent (current-column))))
961
962
             ;; Line is in a function, take indentation from start of
963
             ;; function, ignoring `with'.
964
             ((eq paren-char ?\[)
965
              (goto-char paren)
966
              (forward-line 0)
967
              (looking-at "\\(\\s-*with\\s-\\)?\\s-*")
968
              (goto-char (match-end 0))
969
              (setq indent
970
                    (+ (current-column)
971
                       (if (eq this-char ?\]) 0 inform-indent-level))))
972
973
             ;; Line is in a block: take indentation from block.
974
             (t
975
              (goto-char paren)
976
              (if (eq (inform-preceding-char) ?\))
977
                  (forward-sexp -1))
978
              (forward-line 0)
979
              (skip-syntax-forward " ")
980
981
              (setq indent
982
                    (+ (current-column)
983
                       (if (memq this-char '(?} ?{))
984
                           0
985
                         inform-indent-level)))
986
              ))
987
988
            ;; We calculated the indentation for the start of the
989
            ;; string; correct this for the remainder of the string if
990
            ;; appropriate.
991
            (cond 
992
             ((eq syntax 'string)
993
              ;; do conditional line-up
994
              (cond 
995
               ((numberp inform-strings-line-up-p)
996
                (setq indent inform-strings-line-up-p))
997
               ((eq inform-strings-line-up-p 'char)
998
                (setq indent (1+ string-indent)))
999
               (inform-strings-line-up-p 
1000
                (setq indent string-indent))
1001
               ((not cont-p)
1002
                (goto-char string-start)
1003
                (let ((token (inform-preceding-token)))
1004
                  (if (not (memq token inform-statement-terminators))
1005
                      (setq indent
1006
                            (+ indent inform-indent-cont-statement)))))))
1007
             
1008
             ;; Indent for label, if any.
1009
             ((eq syntax 'label)
1010
              (setq indent (+ indent inform-indent-label-offset))))))
1011
1012
        ;; Handle comments specially if told to line them up
1013
        (if (looking-at (concat "\\s-*" comment-start))
1014
            (setq indent (inform-line-up-comment indent)))
1015
                        
1016
        indent)))))
1017
1018
1019
(defun inform-line-up-comment (current-indent)
1020
  "Return the indentation to line up this comment with the previous one.
1021
If inform-comments-line-up-p is nil, or the preceeding lines do not contain
1022
comments, return CURRENT-INDENT."
1023
  (if inform-comments-line-up-p
1024
      (save-excursion
1025
        (let ((indent current-indent)
1026
              done limit)
1027
          (while (and (not done)
1028
                      (> (point) 1))
1029
            (forward-line -1)
1030
            (setq limit (point))
1031
            (cond ((looking-at (concat "\\s-*" comment-start))
1032
                   ;; a full-line comment, keep searching
1033
                   nil)
1034
                  ((and
1035
                    (or (end-of-line) t) 
1036
                    (re-search-backward comment-start limit t)
1037
                    (eq (car (inform-syntax-class)) 'comment))
1038
                   ;; a line with a comment char at the end
1039
                   ;; that is not part of the code
1040
                   (setq indent (current-column))
1041
                   (setq done t))
1042
                  (t
1043
                   ;; a non-comment line so we do not need to change
1044
                   (setq done t))))
1045
          indent))
1046
    current-indent))
1047
      
1048
1049
;; Modifies whitespace to the left of point so that the character after
1050
;; point is at COLUMN.  If this is impossible, one whitespace character
1051
;; is left.  Avoids changing buffer gratuitously, and returns non-NIL if
1052
;; it actually changed the buffer.  If a change is made, point is moved
1053
;; to the end of any inserted or deleted whitespace.  (If not, it may be
1054
;; moved at random.)
1055
1056
(defun inform-indent-to (column)
1057
  (let ((col (current-column)))
1058
    (cond ((eq col column) nil)
1059
          ((< col column) (indent-to column) t)
1060
          (t (let ((p (point))
1061
                   (mincol (progn (skip-syntax-backward " ")
1062
                                  (current-column))))
1063
               (if (eq mincol (1- col))
1064
                   nil
1065
                 (delete-region (point) p)
1066
                 (indent-to (max (if (bolp) mincol (1+ mincol)) column))
1067
                 t))))))
1068
1069
;; Indent the line containing point; DATA is assumed to have been
1070
;; returned from `inform-syntax-class', called at the *start* of the
1071
;; current line.  It is assumed that point is at the start of the line.
1072
;; Fixes up the spacing on `has', `with', `object', `nearby', `private'
1073
;; and `class' lines.  Returns T if a change was made, NIL otherwise.
1074
;; Moves point.
1075
1076
(defun inform-do-indent-line (data)
1077
  (skip-syntax-forward " ")
1078
  (let ((changed-p (inform-indent-to (inform-calculate-indentation data)))
1079
        (syntax (car data)))
1080
1081
    ;; Fix up space if appropriate, return changed flag.
1082
    (or
1083
     (cond
1084
      ((and (memq syntax '(directive has with class private))
1085
            inform-indent-fixup-space
1086
            (looking-at
1087
             "\\(object\\|class\\|nearby\\|has\\|with\\|private\\)\\(\\s-+\\|$\\)"))
1088
       (goto-char (match-end 0))
1089
       (inform-indent-to inform-indent-property))
1090
      ((and (eq syntax 'action)
1091
            inform-indent-action-column
1092
            (or (looking-at "\\*.*\\(->\\)")
1093
                (looking-at "\\*.*\\($\\)")))
1094
       (goto-char (match-beginning 1))
1095
       (inform-indent-to inform-indent-action-column))
1096
      (t nil))
1097
     changed-p)))
1098
1099
;; Calculate and return the indentation for a comment (assume point is
1100
;; on the comment).
1101
1102
(defun inform-comment-indent ()
1103
  (skip-syntax-backward " ")
1104
  (if (bolp)
1105
      (inform-calculate-indentation (inform-syntax-class))
1106
    (max (1+ (current-column)) comment-column)))
1107
1108
;; Indent line containing point. 
1109
;; Keep point at the "logically" same place, unless point was before
1110
;; new indentation, in which case place point at indentation.
1111
1112
(defun inform-indent-line ()
1113
  (let ((oldpos (- (point-max) (point))))
1114
    (forward-line 0)
1115
    (inform-do-indent-line (inform-syntax-class))
1116
    (and (< oldpos (- (point-max) (point)))
1117
         (goto-char (- (point-max) oldpos)))))
1118
1119
;; Indent all the lines in region.
1120
1121
(defun inform-indent-region (start end)
1122
  (save-restriction
1123
    (let ((endline (progn (goto-char (max end start))
1124
                          (or (bolp) (end-of-line))
1125
                          (point)))
1126
          data linestart)
1127
      (narrow-to-region (point-min) endline)
1128
      (goto-char (min start end))
1129
      (forward-line 0)
1130
      (while (not (eobp))
1131
        (setq data (if data (inform-syntax-class linestart data)
1132
                     (inform-syntax-class))
1133
              linestart (point))
1134
        (inform-do-indent-line data)
1135
        (forward-line 1)))))
1136
1137

1138
;;;
1139
;;; Filling paragraphs
1140
;;;
1141
1142
;; Fill quoted string or comment containing point.  To fill a quoted
1143
;; string, point must be between the quotes.  Deals appropriately with
1144
;; trailing backslashes.
1145
1146
(defun inform-fill-paragraph (&optional arg)
1147
  (let* ((data (inform-syntax-class))
1148
         (syntax (car data))
1149
         (case-fold-search t))
1150
    (cond ((eq syntax 'comment)
1151
           (if (save-excursion
1152
                 (forward-line 0)
1153
                 (looking-at "\\s-*!+\\s-*"))
1154
               (let ((fill-prefix (match-string 0)))
1155
                 (fill-paragraph nil)
1156
                 t)
1157
             (error "Can't fill comments not at start of line.")))
1158
          ((eq syntax 'string)
1159
           (save-excursion
1160
             (let* ((indent-col (prog2
1161
                                    (insert ?\n)
1162
                                    (inform-calculate-indentation data)
1163
                                  (delete-backward-char 1)))
1164
                    (start (search-backward "\""))
1165
                    (end (search-forward "\"" nil nil 2))
1166
                    (fill-column (- fill-column 2))
1167
                    linebeg)
1168
               (save-restriction
1169
                 (narrow-to-region (point-min) end)
1170
1171
                 ;; Fold all the lines together, removing backslashes
1172
                 ;; and multiple spaces as we go.
1173
                 (subst-char-in-region start end ?\n ? )
1174
                 (subst-char-in-region start end ?\\ ? )
1175
                 (subst-char-in-region start end ?\t ? )
1176
                 (goto-char start)
1177
                 (while (re-search-forward "  +" end t)
1178
                   (delete-region (match-beginning 0) (1- (match-end 0))))
1179
1180
                 ;; Split this line; reindent after first split,
1181
                 ;; otherwise indent to point where first split ended
1182
                 ;; up.
1183
                 (goto-char start)
1184
                 (setq linebeg start)
1185
                 (while (not (eobp))
1186
                   (move-to-column (1+ fill-column))
1187
                   (if (eobp)
1188
                       nil
1189
                     (skip-chars-backward "^ " linebeg)
1190
                     (if (eq (point) linebeg)
1191
                         (progn
1192
                           (skip-chars-forward "^ ")
1193
                           (skip-chars-forward " ")))
1194
                     (insert "\n")
1195
                     (indent-to-column indent-col 1)
1196
                     (setq linebeg (point))))))
1197
1198
             ;; Return T so that `fill-paragaph' doesn't try anything.
1199
             t))
1200
1201
          (t (error "Point is neither in a comment nor a string.")))))
1202
1203

1204
;;;
1205
;;; Tags
1206
;;;
1207
1208
;; Return the project file to which the current file belongs.  This is
1209
;; either the value of `inform-project-file', the current file.
1210
1211
(defun inform-project-file ()
1212
  (or inform-project-file (buffer-file-name)))
1213
1214
;; Builds a list of files in the current project and returns it.  It
1215
;; recursively searches through included files, but tries to avoid
1216
;; loops.
1217
1218
(defun inform-project-file-list ()
1219
  (let* ((project-file (expand-file-name (inform-project-file)))
1220
         (project-dir (file-name-directory project-file))
1221
         (in-file-list (list project-file))
1222
         out-file-list
1223
         (temp-buffer (generate-new-buffer "*Inform temp*")))
1224
    (message "Building list of files in project...")
1225
    (save-excursion
1226
      (while in-file-list
1227
        (if (member (car in-file-list) out-file-list)
1228
            nil
1229
          (set-buffer temp-buffer)
1230
          (erase-buffer)
1231
          (insert-file-contents (car in-file-list))
1232
          (setq out-file-list (cons (car in-file-list) out-file-list)
1233
                in-file-list (cdr in-file-list))
1234
          (goto-char (point-min))
1235
          (while (re-search-forward "\\<#?include\\s-+\">\\([^\"]+\\)\"" nil t)
1236
            (let ((file (match-string 1)))
1237
              ;; We need to duplicate Inform's file-finding algorithm:
1238
              (if (not (string-match "\\." file))
1239
                  (setq file (concat file ".inf")))
1240
              (if (not (file-name-absolute-p file))
1241
                  (setq file (expand-file-name file project-dir)))
1242
              (setq in-file-list (cons file in-file-list))))))
1243
      (kill-buffer nil))
1244
    (message "Building list of files in project...done")
1245
    out-file-list))
1246
1247
;; Visit tags table for current project, if it exists, or do nothing if
1248
;; there is no current project, or no tags table.
1249
1250
(defun inform-auto-load-tags-table ()
1251
  (let (tf (project (inform-project-file)))
1252
    (if project
1253
        (progn
1254
          (setq tf (expand-file-name "TAGS" (file-name-directory project)))
1255
          (if (file-readable-p tf)
1256
              ;; visit-tags-table seems to just take first parameter in XEmacs
1257
              (visit-tags-table tf))))))
1258
1259
(defun inform-retagify ()
1260
  "Create a tags table for the files in the current project.
1261
The current project contains all the files included using Inform's
1262
`Include \">file\";' syntax by the project file, which is that given by
1263
the variable `inform-project-file' \(if this is set\), or the current
1264
file \(if not\).  Files included recursively are included in the tags
1265
table."
1266
  (interactive)
1267
  (let* ((project-file (inform-project-file))
1268
         (project-dir (file-name-directory project-file))
1269
         (files (inform-project-file-list))
1270
         (tags-file (expand-file-name "TAGS" project-dir)))
1271
    (message "Running external tags program...")
1272
1273
    ;; Uses call-process to work on windows/nt systems (not tested)
1274
    ;; Regexp matches routines or object/class definitions
1275
    (apply (function call-process)
1276
           inform-etags-program
1277
           nil nil nil
1278
           "--regex=/\\([oO]bject\\|[nN]earby\\|[cC]lass\\|\\[\\)\\([ \\t]*->\\)*[ \\t]*\\([A-Za-z0-9_]+\\)/"
1279
           (concat "--output=" tags-file)
1280
           "--language=none"
1281
           files)
1282
        
1283
    (message "Running external tags program...done")
1284
    (inform-auto-load-tags-table)))
1285
1286
1287
1288

1289
;;;
1290
;;; Electric keys
1291
;;;
1292
1293
(defun inform-toggle-auto-newline (arg)
1294
  "Toggle auto-newline feature.
1295
Optional numeric ARG, if supplied turns on auto-newline when positive,
1296
turns it off when negative, and just toggles it when zero."
1297
  (interactive "P")
1298
  (setq inform-auto-newline
1299
        (if (or (not arg)
1300
                (zerop (setq arg (prefix-numeric-value arg))))
1301
            (not inform-auto-newline)
1302
          (> arg 0))))
1303
1304
(defun inform-electric-key (arg)
1305
  "Insert the key typed and correct indentation."
1306
  (interactive "P")
1307
  (if (and (not arg) (eolp))
1308
      (progn
1309
        (self-insert-command 1)
1310
        (inform-indent-line)
1311
        (end-of-line))
1312
    (self-insert-command (prefix-numeric-value arg))))
1313
1314
(defun inform-electric-semi (arg)
1315
  "Insert the key typed and correct line's indentation, as for semicolon.
1316
Special handling does not occur inside strings and comments.
1317
Inserts newline after the character if `inform-auto-newline' is non-NIL."
1318
  (interactive "P")
1319
  (if (and (not arg)
1320
           (eolp)
1321
           (let ((data (inform-syntax-class)))
1322
             (not (memq (car data) '(string comment)))))
1323
      (progn
1324
        (self-insert-command 1)
1325
        (inform-indent-line)
1326
        (end-of-line)
1327
        (if inform-auto-newline (newline-and-indent)))
1328
    (self-insert-command (prefix-numeric-value arg))))
1329
1330
(defun inform-electric-comma (arg)
1331
  "Insert the key typed and correct line's indentation, as for comma.
1332
Special handling only occurs in object declarations.
1333
Inserts newline after the character if `inform-auto-newline' is non-NIL."
1334
  (interactive "P")
1335
  (if (and (not arg)
1336
           (eolp)
1337
           (let ((data (inform-syntax-class)))
1338
             (and (not (memq (car data) '(string comment)))
1339
                  (nth 1 data)
1340
                  (zerop (car (nth 3 data))))))
1341
      (progn
1342
        (self-insert-command 1)
1343
        (inform-indent-line)
1344
        (end-of-line)
1345
        (if inform-auto-newline (newline-and-indent)))
1346
    (self-insert-command (prefix-numeric-value arg))))
1347
1348
(defun inform-electric-brace (arg)
1349
  "Insert the key typed and correct line's indentation.
1350
Insert newlines before and after if `inform-auto-newline' is non-NIL."
1351
  ;; This logic is the same as electric-c-brace.
1352
  (interactive "P")
1353
  (let (insertpos)
1354
    (if (and (not arg)
1355
             (eolp)
1356
             (let ((data (inform-syntax-class)))
1357
               (memq (car data) '(code label)))
1358
             (or (save-excursion (skip-syntax-backward " ") (bolp))
1359
                 (if inform-auto-newline
1360
                     (progn (inform-indent-line) (newline) t) nil)))
1361
        (progn
1362
          (insert last-command-char)
1363
          (inform-indent-line)
1364
          (end-of-line)
1365
          (if (and inform-auto-newline (/= last-command-char ?\]))
1366
              (progn
1367
                (newline)
1368
                (setq insertpos (1- (point)))
1369
                (inform-indent-line)))
1370
          (save-excursion
1371
            (if insertpos (goto-char insertpos))
1372
            (delete-char -1))))
1373
    (if insertpos
1374
        (save-excursion
1375
          (goto-char (1- insertpos))
1376
          (self-insert-command (prefix-numeric-value arg)))
1377
      (self-insert-command (prefix-numeric-value arg)))))
1378
1379

1380
;;;
1381
;;; Miscellaneous
1382
;;;
1383
1384
(defun inform-next-object (&optional arg)
1385
  "Go to the next object or class declaration in the file.
1386
With a prefix arg, go forward that many declarations.
1387
With a negative prefix arg, search backwards."
1388
  (interactive "P")
1389
  (let ((fun 're-search-forward)
1390
        (errstring "more")
1391
        (n (prefix-numeric-value arg)))
1392
    (cond ((< n 0)
1393
           (setq fun 're-search-backward errstring "previous" n (- n)))
1394
          ((looking-at inform-real-object-regexp)
1395
           (setq n (1+ n))))
1396
    (prog1
1397
        (funcall fun inform-real-object-regexp nil 'move n)
1398
      (forward-line 0))))
1399
1400
;; This function doubles as an `imenu-prev-name' function, so when
1401
;; called noninteractively it must return T if it was successful and NIL
1402
;; if not.  Argument NIL must correspond to moving backwards by 1.
1403
1404
(defun inform-prev-object (&optional arg)
1405
  "Go to the previous object or class declaration in the file.
1406
With a prefix arg, go back many declarations.
1407
With a negative prefix arg, go forwards."
1408
  (interactive "P")
1409
  (inform-next-object (- (prefix-numeric-value arg))))
1410
1411
(defun inform-imenu-extract-name ()
1412
  (if (looking-at
1413
       "^#?\\(object\\|nearby\\|class\\)\\s-+\\(->\\s-+\\)*\\(\\(\\w\\|\\s_\\)+\\)")
1414
      (concat (if (string= "class" (downcase (match-string 1)))
1415
                  "Class ")
1416
              (buffer-substring-no-properties (match-beginning 3)
1417
                                              (match-end 3)))))
1418
1419

1420
;;;
1421
;;; Build and run project
1422
;;;
1423
1424
(defun inform-build-project ()
1425
  "Compile the current Inform project.
1426
The current project is given by `inform-project-file', or the current
1427
file if this is NIL."
1428
  (interactive)
1429
  (let ((project-file (file-name-nondirectory (inform-project-file))))
1430
    (compile
1431
     (concat inform-command
1432
             (if (and inform-libraries-directory
1433
                      (file-directory-p inform-libraries-directory))
1434
                 (concat " +" inform-libraries-directory)
1435
               "")
1436
             ;; Note the use of Microsoft style errors.  The
1437
             ;; Archimedes-style errors don't give the correct file
1438
             ;; name.
1439
             " " inform-command-options " -E1 "
1440
             (if (string-match "\\`[^.]+\\(\\.inf\\'\\)" project-file)
1441
                 (substring project-file 0 (match-beginning 1))
1442
               project-file)))))
1443
1444
(defun inform-run-project ()
1445
  "Run the current Inform project using `inform-interpreter-command'.
1446
The current project is given by `inform-project-file', or the current
1447
file if this is NIL.  Will kill any running interpreter if
1448
`inform-interpreter-kill-old-process' is non-NIL.  Switches to the
1449
interpreter's output buffer if `inform-interpreter-is-graphical' is
1450
NIL."
1451
  (interactive)
1452
  (let* ((project-file (inform-project-file))
1453
         (story-file-base (if (string-match "\\`[^.]+\\(\\.inf\\'\\)"
1454
                                            project-file)
1455
                              (substring project-file 0 (match-beginning 1))
1456
                            project-file))
1457
         (story-file (concat story-file-base
1458
                             (if (string-match "-v8" inform-command-options)
1459
                                 ".z8"
1460
                               ".z5")))
1461
         (name "Inform interpreter"))
1462
    (if  (or (symbolp inform-interpreter-command)
1463
             (functionp inform-interpreter-command))
1464
        ;; Emacs interpreter (or custom function)
1465
        (funcall inform-interpreter-command story-file)
1466
      ;; inform-interpreter-command is truly a command
1467
      (let* ((buffer (get-buffer-create (concat "*" name "*")))
1468
             (proc (get-buffer-process buffer)))
1469
        (and inform-interpreter-kill-old-process
1470
             proc
1471
             (kill-process proc))
1472
        (if (or inform-interpreter-is-graphical
1473
                (eq window-system 'w32)) ; Windows can't handle
1474
                                        ; term-exec anyway
1475
            (progn
1476
              ;; X gets confused if an application is restarted too quickly
1477
              ;; Assume X if not Win32
1478
              (unless (eq window-system 'w32)
1479
                (message "Waiting for X...")
1480
                ;; On my system 0.1 seconds was enough - double it for safety
1481
                (sleep-for 0.2)
1482
                (message ""))
1483
              (when (or inform-interpreter-kill-old-process
1484
                        (not proc))
1485
                (apply (function start-process)
1486
                       name buffer inform-interpreter-command
1487
                       ;; Some shells barf on "empty" arguments
1488
                       (if (string-equal "" inform-interpreter-options)
1489
                           (list story-file)
1490
                         (list inform-interpreter-options
1491
                               story-file)))))
1492
          ;; Console-mode 'terp
1493
          (require 'term)
1494
          (when (or inform-interpreter-kill-old-process
1495
                    (not proc))
1496
            (set-buffer buffer)
1497
            (term-mode)
1498
            (erase-buffer)
1499
            (term-exec buffer name inform-interpreter-command nil
1500
                       (if (string-equal "" inform-interpreter-options)
1501
                           (list story-file)
1502
                         (list inform-interpreter-options
1503
                               story-file)))
1504
            (term-char-mode)
1505
            (term-pager-disable))
1506
          (switch-to-buffer buffer)
1507
          (goto-char (point-max)))))))
1508
1509
1510

1511
;;;
1512
;;; Spell checking
1513
;;;
1514
1515
(defun inform-spell-check-buffer ()
1516
  "Spellcheck all strings in the buffer using ispell."
1517
  (interactive)
1518
  (let (start (spell-continue t))
1519
    (save-excursion
1520
      (goto-char (point-min))
1521
      (while (and (search-forward "\"" nil t)
1522
                  spell-continue)
1523
        (if (and (eq (car (inform-syntax-class)) 'string)
1524
                 ;; don't spell check include directives etc
1525
                 (not (save-excursion
1526
                        (forward-line 0)
1527
                        (looking-at inform-directive-regexp))))
1528
            (progn
1529
              (forward-char -1)         ; move point to quotation mark
1530
              (setq start (point))
1531
              (forward-sexp)
1532
              (ispell-region start (point))
1533
              ;; If user quit out (eg by pressing q while in ispell)
1534
              ;; don't continue looking for strings to check.
1535
              (setq spell-continue
1536
                    (and ispell-process
1537
                         (eq (process-status ispell-process) 'run)))))))))
1538
        
1539
1540

1541
(provide 'inform-mode)
1542
1543
;;; inform-mode.el ends here