~ubuntu-branches/ubuntu/hoary/inform/hoary

« back to all changes in this revision

Viewing changes to contrib/inform-mode.el

  • Committer: Bazaar Package Importer
  • Author(s): Mark Baker
  • Date: 2004-03-29 23:52:44 UTC
  • Revision ID: james.westby@ubuntu.com-20040329235244-fox1z1yv7d6vojoo
Tags: upstream-6.30
ImportĀ upstreamĀ versionĀ 6.30

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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