-
Notifications
You must be signed in to change notification settings - Fork 3
/
inform-mode.el
1636 lines (1396 loc) · 61.8 KB
/
inform-mode.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; inform-mode.el --- Major mode for Inform 6 interactive fiction code
;; Author: Rupert Lane <rupert@rupert-lane.org>
;; Gareth Rees <Gareth.Rees@cl.cam.ac.uk>
;; Michael Fessler
;; Created: 1 Dec 1994
;; Version: 1.6.2
;; Released: 10-Oct-2013
;; Url: http://www.rupert-lane.org/inform-mode/
;; Keywords: languages
;;; Copyright:
;; Original version copyright (c) by Gareth Rees 1996
;; Portions copyright (c) by Michael Fessler 1997-1998
;; Portions copyright (c) by Rupert Lane 1999-2013
;; inform-mode is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; inform-mode is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;; Commentary:
;; Inform is a compiler for adventure games by Graham Nelson,
;; available at
;; http://www.inform-fiction.org/inform6.html
;;
;; This file implements a major mode for editing Inform 6 programs. It
;; understands most Inform syntax and is capable of indenting lines
;; and formatting quoted strings. Type `C-h m' within Inform mode for
;; more details.
;;
;; Because Inform header files use the extension ".h" just as C header
;; files do, the function `inform-maybe-mode' is provided. It looks at
;; the contents of the current buffer; if it thinks the buffer is in
;; Inform, it selects inform-mode; otherwise it selects the mode given
;; by the variable `inform-maybe-other'.
;; Latest version of this mode can be found at
;; http://www.rupert-lane.org/inform-mode/
;; Please send any bugs or comments to rupert@rupert-lane.org
;;; History:
;; See the NEWS file in the distribution
;; or http://www.rupert-lane.org/inform-mode/news.html
;;; Code:
(require 'font-lock)
(require 'regexp-opt)
(require 'ispell)
(require 'term)
(require 'compile)
(require 'imenu)
;;;
;;; Customize support
;;;
(defgroup inform-mode nil
"Settings for Inform source code."
:group 'languages)
(defgroup inform-mode-indent nil
"Customize indentation and highlighting of Inform source code."
:group 'inform-mode)
(defgroup inform-mode-build-run nil
"Customize build and run options for Inform code."
:group 'inform-mode)
;;;
;;; General variables
;;;
(defconst inform-mode-version "1.6.2")
(defcustom inform-maybe-other 'c-mode
"*`inform-maybe-mode' runs this if current file is not in Inform mode."
:type 'function
:group 'inform-mode)
(defcustom inform-startup-message t
"*Non-nil means display a message when Inform mode is loaded."
:type 'boolean
:group 'inform-mode)
(defcustom inform-auto-newline t
"*Non-nil means automatically newline before/after braces, after semicolons.
If you do not want a leading newline before opening braces then use:
\(define-key inform-mode-map \"{\" 'inform-electric-semi\)"
:type 'boolean
:group 'inform-mode)
(defvar inform-mode-map nil
"Keymap for Inform mode.")
(if inform-mode-map nil
(let ((map (make-sparse-keymap "Inform")))
(setq inform-mode-map (make-sparse-keymap))
(define-key inform-mode-map "\C-m" 'newline-and-indent)
(define-key inform-mode-map "\177" 'backward-delete-char-untabify)
(define-key inform-mode-map "\C-c\C-r" 'inform-retagify)
(define-key inform-mode-map "\C-c\C-t" 'visit-tags-table)
(define-key inform-mode-map "\C-c\C-b" 'inform-build-project)
(define-key inform-mode-map "\C-c\C-c" 'inform-run-project)
(define-key inform-mode-map "\C-c\C-a" 'inform-toggle-auto-newline)
(define-key inform-mode-map "\C-c\C-s" 'inform-spell-check-buffer)
(define-key inform-mode-map "\M-n" 'inform-next-object)
(define-key inform-mode-map "\M-p" 'inform-prev-object)
(define-key inform-mode-map "{" 'inform-electric-brace)
(define-key inform-mode-map "}" 'inform-electric-brace)
(define-key inform-mode-map "]" 'inform-electric-brace)
(define-key inform-mode-map ";" 'inform-electric-semi)
(define-key inform-mode-map ":" 'inform-electric-key)
(define-key inform-mode-map "!" 'inform-electric-key)
(define-key inform-mode-map "," 'inform-electric-comma)
(define-key inform-mode-map [menu-bar] (make-sparse-keymap))
(define-key inform-mode-map [menu-bar inform] (cons "Inform" map))
(define-key map [separator4] '("--" . nil))
(define-key map [inform-spell-check-buffer]
'("Spellcheck buffer" . inform-spell-check-buffer))
(define-key map [ispell-region] '("Spellcheck region" . ispell-region))
(define-key map [ispell-word] '("Spellcheck word" . ispell-word))
(define-key map [separator3] '("--" . nil))
(define-key map [load-tags] '("Load tags table" . visit-tags-table))
(define-key map [retagify] '("Rebuild tags table" . inform-retagify))
(define-key map [build] '("Build project" . inform-build-project))
(define-key map [run] '("Run project" . inform-run-project))
(define-key map [separator2] '("--" . nil))
(define-key map [next-object] '("Next object" . inform-next-object))
(define-key map [prev-object] '("Previous object" . inform-prev-object))
(define-key map [separator1] '("--" . nil))
(define-key map [comment-region] '("Comment Out Region" . comment-region))
(put 'comment-region 'menu-enable 'mark-active)
(define-key map [indent-region] '("Indent Region" . indent-region))
(put 'indent-region 'menu-enable 'mark-active)
(define-key map [indent-line] '("Indent Line" . indent-for-tab-command))))
(defvar inform-mode-abbrev-table nil
"Abbrev table used while in Inform mode.")
(define-abbrev-table 'inform-mode-abbrev-table nil)
;;;
;;; Indentation parameters
;;;
(defcustom inform-indent-property 8
"*Indentation of the start of a property declaration."
:type 'integer
:group 'inform-mode-indent)
(put 'inform-indent-property 'safe-local-variable 'integerp)
(defcustom inform-indent-has-with-class 1
"*Indentation of has/with/class lines in object declarations."
:type 'integer
:group 'inform-mode-indent)
(put 'inform-indent-has-with-class 'safe-local-variable 'integerp)
(defcustom inform-indent-level 4
"*Indentation of lines of block relative to first line of block."
:type 'integer
:group 'inform-mode-indent)
(put 'inform-indent-level 'safe-local-variable 'integerp)
(defcustom inform-indent-label-offset -3
"*Indentation of label relative to where it should be."
:type 'integer
:group 'inform-mode-indent)
(put 'inform-indent-label-offset 'safe-local-variable 'integerp)
(defcustom inform-indent-cont-statement 4
"*Indentation of continuation relative to start of statement."
:type 'integer
:group 'inform-mode-indent)
(put 'inform-indent-cont-statement 'safe-local-variable 'integerp)
(defcustom inform-indent-fixup-space t
"*If non-NIL, fix up space in object declarations."
:type 'boolean
:group 'inform-mode-indent)
(put 'inform-indent-fixup-space 'safe-local-variable 'booleanp)
(defcustom inform-indent-action-column 40
"*Column at which action names should be placed in verb declarations."
:type 'integer
:group 'inform-mode-indent)
(put 'inform-indent-action-column 'safe-local-variable 'integerp)
(defcustom inform-comments-line-up-p nil
"*If non-nil, comments spread over several lines will line up with the first."
:type 'boolean
:group 'inform-mode-indent)
(put 'inform-comments-line-up-p 'safe-local-variable 'booleanp)
(defcustom inform-strings-line-up-p nil
"*Variable controlling indentation of multi-line strings.
If nil (default), string will be indented according to context.
If a number, will always set the indentation to that column.
If 'char', will line up with the first character of the string.
If 'quote', or other non-nil value, will line up with open quote on
first line."
:type '(radio (const :tag "Indent according to context" nil)
(integer :tag "Column to indent to")
(const :tag "Line up with first character of string" char)
(const :tag "Line up with open quote on first line" quote))
:group 'inform-mode-indent)
(put 'inform-strings-line-up-p 'safe-local-variable 'sexp)
(defcustom inform-indent-semicolon t
"*If nil, a semicolon on a line of its own will not be indented."
:type 'boolean
:group 'inform-mode-indent)
(put 'inform-indent-semicolon 'safe-local-variable 'booleanp)
;;;
;;; Syntax variables
;;;
(defvar inform-mode-syntax-table nil
"Syntax table to use in Inform mode buffers.")
(if inform-mode-syntax-table
nil
(setq inform-mode-syntax-table (make-syntax-table))
(modify-syntax-entry ?\\ "\\" inform-mode-syntax-table)
(modify-syntax-entry ?\n ">" inform-mode-syntax-table)
(modify-syntax-entry ?! "<" inform-mode-syntax-table)
(modify-syntax-entry ?# "_" inform-mode-syntax-table)
(modify-syntax-entry ?% "." inform-mode-syntax-table)
(modify-syntax-entry ?& "." inform-mode-syntax-table)
(modify-syntax-entry ?\' "." inform-mode-syntax-table)
(modify-syntax-entry ?* "." inform-mode-syntax-table)
(modify-syntax-entry ?- "." inform-mode-syntax-table)
(modify-syntax-entry ?/ "." inform-mode-syntax-table)
(modify-syntax-entry ?\; "." inform-mode-syntax-table)
(modify-syntax-entry ?< "." inform-mode-syntax-table)
(modify-syntax-entry ?= "." inform-mode-syntax-table)
(modify-syntax-entry ?> "." inform-mode-syntax-table)
(modify-syntax-entry ?+ "." inform-mode-syntax-table)
(modify-syntax-entry ?| "." inform-mode-syntax-table)
(modify-syntax-entry ?^ "w" inform-mode-syntax-table))
;;;
;;; Build and run variables
;;;
(defcustom inform-project-file nil
"*The top level Inform project file to which the current file belongs."
:type '(radio (file :tag "Project file name")
(const :tag "Disabled" nil))
:group 'inform-mode-build-run)
(make-variable-buffer-local 'inform-project-file)
(defcustom inform-autoload-tags t
"*Non-nil means automatically load tags table when entering Inform mode."
:type 'boolean
:group 'inform-mode-build-run)
(defcustom inform-etags-program "etags"
"*The shell command with which to run the etags program."
:type 'string
:group 'inform-mode-build-run)
(defcustom inform-command "inform"
"*The shell command with which to run the Inform compiler."
:type 'string
:group 'inform-mode-build-run)
(defcustom inform-libraries-directory nil
"*If non-NIL, gives the directory in which libraries are found."
:type '(radio (directory :tag "Library directory")
(const :tag "Disabled" nil))
:group 'inform-mode-build-run)
(defcustom inform-command-options ""
"*Options with which to call the Inform compiler."
:type 'string
:group 'inform-mode-build-run)
(defcustom inform-interpreter-command "frotz"
"*The command with which to run the ZCode interpreter.
If a string, the name of a command. If a symbol or a function value, an
Emacs-lisp function to be called with the name of the story file."
:type '(choice (string :tag "Command to run the ZCode interpreter")
(function :tag "Emacs-lisp function to run on the file"))
:group 'inform-mode-build-run)
(defcustom inform-interpreter-options ""
"*Additional options with which to call the ZCode interpreter.
Only used if `inform-interpreter-command' is a string."
:type 'string
:group 'inform-mode-build-run)
(defcustom inform-interpreter-kill-old-process t
"*Whether to kill the old interpreter process when starting a new one."
:type 'boolean
:group 'inform-mode-build-run)
(defcustom inform-interpreter-is-graphical nil
"*Controls whether `inform-interpreter-command' will be run in a buffer.
If NIL, `inform-run-project' will switch to the interpreter buffer after
running the interpreter."
:type 'boolean
:group 'inform-mode-build-run)
(defvar inform-compilation-error-regexp-alist
'((inform-e1
"^[ \t]*\\(\\(?:[a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\)): ?\
\\(?:\\(Error\\)\\|\\(Warning\\)\\):"
1 2 nil (4)))
"Alist matching compilation errors for inform -E1 style output.")
;;; Keyword definitions-------------------------------------------------------
;; These are used for syntax and font-lock purposes.
;; They combine words used in Inform 5 and Inform 6 for full compatibility.
;; You can add new keywords directly to this list as the regexps for
;; font-locking are defined when this file is byte-compiled or eval'd.
(eval-and-compile
(defvar inform-directive-list
'("abbreviate" "array" "attribute" "btrace" "class" "constant"
"default" "dictionary" "end" "endif" "etrace" "extend" "fake_action"
"global" "ifdef" "iffalse" "ifndef" "ifnot" "iftrue" "ifv3" "ifv5"
"import" "include" "link" "listsymbols" "listdict" "listverbs"
"lowstring" "ltrace" "message" "nearby" "nobtrace" "noetrace"
"noltrace" "notrace" "object" "property" "release" "replace" "serial"
"statusline" "stub" "switches" "system_file" "trace" "verb"
"version" "zcharacter")
"List of Inform directives that shouldn't appear embedded in code.")
(defvar inform-defining-list
'("[" "array" "attribute" "class" "constant" "fake_action" "global"
"lowstring" "nearby" "object" "property")
"List of Inform directives that define a variable/constant name.
Used to build a font-lock regexp; the name defined must follow the
keyword.")
(defvar inform-attribute-list
'("absent" "animate" "clothing" "concealed" "container" "door"
"edible" "enterable" "female" "general" "light" "lockable" "locked"
"male" "moved" "neuter" "on" "open" "openable" "pluralname" "proper"
"scenery" "scored" "static" "supporter" "switchable" "talkable"
"transparent" "visited" "workflag" "worn")
"List of Inform attributes defined in the library.")
(defvar inform-property-list
'("n_to" "s_to" "e_to" "w_to" "ne_to" "se_to" "nw_to" "sw_to" "u_to"
"d_to" "in_to" "out_to" "add_to_scope" "after" "article" "articles"
"before" "cant_go" "capacity" "daemon" "describe" "description"
"door_dir" "door_to" "each_turn" "found_in" "grammar" "initial"
"inside_description" "invent" "life" "list_together" "name" "number"
"orders" "parse_name" "plural" "react_after" "react_before"
"short_name" "short_name_indef" "time_left" "time_out" "when_closed"
"when_open" "when_on" "when_off" "with_key")
"List of Inform properties defined in the library.")
(defvar inform-code-keyword-list
'("box" "break" "continue" "do" "else" "font off" "font on" "for"
"give" "has" "hasnt" "if" "in" "inversion" "jump" "move" "new_line"
"notin" "objectloop" "ofclass" "or" "print" "print_ret" "provides"
"quit" "read" "remove" "restore" "return" "rfalse" "rtrue" "save"
"spaces" "string" "style bold" "style fixed" "style reverse"
"style roman" "style underline" "switch" "to" "until" "while")
"List of Inform code keywords.")
)
;; Some regular expressions are needed at compile-time too so as to
;; avoid postponing the work to load time.
;; To do the work of building the regexps we use regexp-opt with the
;; paren option which ensures the result is enclosed by a grouping
;; construct.
(eval-and-compile
(defun inform-make-regexp (strings)
(regexp-opt strings t)))
(eval-and-compile
(defvar inform-directive-regexp
(concat "\\<#?\\("
(inform-make-regexp inform-directive-list)
"\\)\\>")
"Regular expression matching an Inform directive.")
(defvar inform-defining-list-regexp
(inform-make-regexp inform-defining-list))
(defvar inform-object-regexp
"#?\\<\\(object\\|nearby\\|class\\)\\>"
"Regular expression matching start of object declaration.")
(defvar inform-property-regexp
(concat "\\s-*\\("
(inform-make-regexp inform-property-list)
"\\)")
"Regular expression matching Inform properties."))
(defvar inform-real-object-regexp
(eval-when-compile (concat "^" inform-object-regexp))
"Regular expression matching the start of a real object declaration.
That is, one found at the start of a line.")
(defvar inform-label-regexp "[^]:\"!\(\n]+\\(:\\|,\\)"
"Regular expression matching a label.")
(defvar inform-action-regexp "\\s-*\\*"
"Regular expression matching an action line in a verb declaration.")
(defvar inform-statement-terminators '(?\; ?{ ?} ?: ?\) do else)
"Tokens which precede the beginning of a statement.")
;;;
;;; Font-lock keywords
;;;
(defvar inform-font-lock-defaults
'(inform-font-lock-keywords nil t ((?_ . "w") (?' . "$")) inform-prev-object)
"Font Lock defaults for Inform mode.")
(defface inform-dictionary-word-face
'((((class color) (background light)) (:foreground "Red"))
(((class color) (background dark)) (:foreground "Pink"))
(t (:italic t :bold t)))
"Font lock mode face used to highlight dictionary words."
:group 'inform-mode)
(defvar inform-dictionary-word-face 'inform-dictionary-word-face
"Variable for Font lock mode face used to highlight dictionary words.")
(defvar inform-font-lock-keywords
(eval-when-compile
(list
;; Inform code keywords
;; Handles two keywords in a row, eg 'else return'
(cons (concat "\\b"
(inform-make-regexp inform-code-keyword-list)
"\\b")
'font-lock-keyword-face)
;; Keywords that declare variable or constant names.
(list
(concat "^#?"
inform-defining-list-regexp
"\\s-+\\(->\\s-+\\)*\\(\\(\\w\\|\\s_\\)+\\)")
'(1 font-lock-keyword-face)
'(3 font-lock-function-name-face))
;; Other directives.
(cons inform-directive-regexp 'font-lock-keyword-face)
;; Single quoted strings, length > 1, are dictionary words
'("'\\(\\(-\\|\\w\\)\\(\\(-\\|\\w\\)+\\(//\\w*\\)?\\|//\\w*\\)\\)'"
(1 inform-dictionary-word-face append))
;; Double-quoted dictionary words
'("\\(\\s-name\\s-\\|^Verb\\|^Extend\\|^\\s-+\\*\\)"
("\"\\(\\(-\\|\\w\\)+\\)\"" nil nil
(1 inform-dictionary-word-face t)))
;; More double-quoted dictionary words
'("^\\s-+\"\\(\\(-\\|\\w\\)+\\)\"\\s-+\"\\(\\(-\\|\\w\\)+\\)\""
(1 inform-dictionary-word-face t)
(3 inform-dictionary-word-face t)
("\"\\(\\(-\\|\\w\\)+\\)\"" nil nil
(1 inform-dictionary-word-face t)))
;; `private', `class', `has' and `with' in objects.
'("^\\s-+\\(private\\|class\\|has\\|with\\)\\(\\s-\\|$\\)"
(1 font-lock-keyword-face))
;; Attributes and properties.
(cons (concat "[^#]\\<\\("
(inform-make-regexp (append inform-attribute-list
inform-property-list))
"\\)\\>")
'(1 font-lock-variable-name-face))))
"Expressions to fontify in Inform mode.")
;;;
;;; Inform mode
;;;
;;;###autoload
(defun inform-mode ()
"Major mode for editing Inform programs.
* Inform syntax:
Type \\[indent-for-tab-command] to indent the current line.
Type \\[indent-region] to indent the region.
Type \\[fill-paragraph] to fill strings or comments.
This compresses multiple spaces into single spaces.
* Multi-file projects:
The variable `inform-project-file' gives the name of the root file of
the project \(i.e., the one that you run Inform on\)\; it is best to
set this as a local variable in each file, for example by making
! -*- inform-project-file:\"game.inf\" -*-
the first line of the file.
* Tags tables:
Type \\[inform-retagify] to build \(and load\) a Tags table.
Type \\[visit-tags-table] to load an existing Tags table.
If it exists, and if the variable `inform-autoload-tags' is non-NIL,
the Tags table is loaded on entry to Inform Mode.
With a Tags table loaded, type \\[find-tag] to find the declaration of
the object, class or function under point.
* Navigating in a file:
Type \\[inform-prev-object] to go to the previous object/class declaration.
Type \\[inform-next-object] to go to the next one.
* Compilation:
Type \\[inform-build-project] to build the current project.
Type \\[next-error] to go to the next error.
* Running:
Type \\[inform-run-project] to run the current project in an
interpreter, either as a separate process or in an Emacs terminal buffer.
* Spell checking:
Type \\[inform-spell-check-buffer] to spell check all strings in the buffer.
Type \\[ispell-word] to check the single word at point.
* Key definitions:
\\{inform-mode-map}
* Functions:
inform-maybe-mode
Looks at the contents of a file, guesses whether it is an Inform
program, runs `inform-mode' if so, or `inform-maybe-other' if not.
The latter defaults to `c-mode'. Used for header files which might
be Inform or C programs.
* Miscellaneous user options:
inform-startup-message
Set to nil to inhibit message first time Inform mode is used.
inform-maybe-other
The mode used by `inform-maybe-mode' if it guesses that the file is
not an Inform program.
inform-mode-hook
This hook is run after entry to Inform Mode.
inform-autoload-tags
If non-nil, then a tags table will automatically be loaded when
entering Inform mode.
inform-auto-newline
If non-nil, then newlines are automatically inserted before and
after braces, and after semicolons in Inform code, and after commas
in object declarations.
* User options controlling indentation style:
Values in parentheses are the default indentation style.
inform-indent-property \(8\)
Indentation of a property or attribute in an object declaration.
inform-indent-has-with-class \(1\)
Indentation of has/with/class/private lines in object declaration.
inform-indent-level \(4\)
Indentation of line of code in a block relative to the first line of
the block.
inform-indent-label-offset \(-3\)
Indentation of a line starting with a label, relative to the
indentation if the label were absent.
inform-indent-cont-statement \(4\)
Indentation of second and subsequent lines of a statement, relative
to the first.
inform-indent-fixup-space \(T\)
If non-NIL, fix up space after `Object', `Class', `Nearby', `has',
`private' and `with', so that all the object's properties line up.
inform-indent-action-column \(40\)
Column at which action names should be placed in verb declarations.
If NIL, then action names are not moved.
inform-comments-line-up-p \(NIL\)
If non-NIL, comments spread out over several lines will start on the
same column as the first comment line.
inform-strings-line-up-p \(NIL\)
Variable controlling indentation of multi-line strings.
If nil (default), string will be indented according to context.
If a number, will always set the indentation to that column.
If 'char', will line up with the first character of the string.
If 'quote', or other non-nil value, will line up with open quote on
first line.
* User options to do with compilation:
inform-command
The shell command with which to run the Inform compiler.
inform-libraries-directory
If non-NIL, gives the directory in which the Inform libraries are
found.
inform-command-options
Additional options with which to call the Inform compiler.
* User options to do with an interpreter:
inform-interpreter-command
The command with which to run the ZCode interpreter. Can be a
string (a command to be run), a symbol (name of function to call)
or a function.
inform-interpreter-options
Additional options with which to call the ZCode interpreter. Only
used if `inform-interpreter-command' is a string.
inform-interpreter-kill-old-process
If non-NIL, `inform-run-project' will kill any running interpreter
process and start a new one. If not, will switch to the interpreter's
buffer (if necessary - see documentation for `inform-run-project' for
details).
inform-interpreter-is-graphical
If NIL, `inform-run-project' will switch to the interpreter buffer
after running the interpreter.
* Please send any bugs or comments to rupert@rupert-lane.org"
(interactive)
(if inform-startup-message
(message "Emacs Inform mode version %s." inform-mode-version))
(kill-all-local-variables)
(use-local-map inform-mode-map)
(set-syntax-table inform-mode-syntax-table)
(make-local-variable 'comment-column)
(make-local-variable 'comment-end)
(make-local-variable 'comment-indent-function)
(make-local-variable 'comment-start)
(make-local-variable 'comment-start-skip)
(make-local-variable 'fill-paragraph-function)
(make-local-variable 'font-lock-defaults)
(make-local-variable 'imenu-extract-index-name-function)
(make-local-variable 'imenu-prev-index-position-function)
(make-local-variable 'indent-line-function)
(make-local-variable 'indent-region-function)
(make-local-variable 'parse-sexp-ignore-comments)
(make-local-variable 'require-final-newline)
(setq comment-column 40
comment-end ""
comment-indent-function 'inform-comment-indent
comment-start "!"
comment-start-skip "!+\\s-*"
fill-paragraph-function 'inform-fill-paragraph
font-lock-defaults inform-font-lock-defaults
imenu-extract-index-name-function 'inform-imenu-extract-name
imenu-prev-index-position-function 'inform-prev-object
indent-line-function 'inform-indent-line
indent-region-function 'inform-indent-region
local-abbrev-table inform-mode-abbrev-table
major-mode 'inform-mode
mode-name "Inform"
parse-sexp-ignore-comments t
require-final-newline t)
(auto-fill-mode 1)
(if inform-autoload-tags
(inform-auto-load-tags-table))
(run-hooks 'inform-mode-hook))
;;;###autoload
(defun inform-maybe-mode ()
"Start Inform mode if file is in Inform; `inform-maybe-other' otherwise."
(let ((case-fold-search t))
(if (save-excursion
(re-search-forward
"^\\(!\\|object\\|nearby\\|\\[ \\)"
nil t))
(inform-mode)
(funcall inform-maybe-other))))
;;;
;;; Syntax and indentation
;;;
(defun inform-beginning-of-defun ()
"Go to the start of the current Inform definition.
Just goes to the most recent line with a function beginning [, or
a directive."
(let ((case-fold-search t))
(catch 'found
(end-of-line 1)
(while (re-search-backward "\n[[a-z]" nil 'move)
(forward-char 1)
(if (or (and (looking-at "\\[")
(eq (inform-preceding-char) ?\;))
(looking-at inform-directive-regexp))
(throw 'found nil))
(forward-char -1)))))
(defun inform-preceding-char ()
"Return preceding non-blank, non-comment character in buffer.
It is assumed that point is not inside a string or comment."
(save-excursion
(while (/= (point) (progn (forward-comment -1) (point))))
(skip-syntax-backward " ")
(if (bobp) ?\;
(preceding-char))))
(defun inform-preceding-token ()
"Return preceding non-blank, non-comment token in buffer.
Either the character itself, or the tokens 'do or 'else. It is
assumed that point is not inside a string or comment."
(save-excursion
(while (/= (point) (progn (forward-comment -1) (point))))
(skip-syntax-backward " ")
(if (bobp) ?\;
(let ((p (preceding-char)))
(cond ((and (eq p ?o)
(>= (- (point) 2) (point-min)))
(goto-char (- (point) 2))
(if (looking-at "\\<do") 'do p))
((and (eq p ?e)
(>= (- (point) 4) (point-min)))
(goto-char (- (point) 4))
(if (looking-at "\\<else") 'else p))
(t p))))))
;; `inform-syntax-class' returns a list describing the syntax at point.
;; The returned list is of the form (SYNTAX IN-OBJ SEXPS STATE).
;; SYNTAX is one of
;; directive An Inform directive (given by `inform-directive-list')
;; has The "has" keyword
;; with The "with" keyword
;; class The "class" keyword
;; private The "private" keyword
;; property A property or attribute
;; other Any other line not in a function body
;; string The line begins inside a string
;; comment The line starts with a comment
;; label Line contains a label (i.e. has a colon in it)
;; code Any other line inside a function body
;; blank A blank line
;; action An action line in a verb declaration
;; IN-OBJ is non-NIL if the line appears to be inside an Object, Nearby,
;; or Class declaration.
;; SEXPS is a list of pairs (D . P) where P is the start of a sexp
;; containing point and D is its nesting depth. The pairs are in
;; decreasing order of nesting depth.
;; STATE is the list returned by `parse-partial-sexp'.
;; For reasons of speed, `inform-syntax-class' looks for directives only
;; at the start of lines. If the source contains top-level directives
;; not at the start of lines, or anything else at the start of a line
;; that might be mistaken for a directive, the wrong syntax class may be
;; returned.
;; There are circumstances in which SEXPS might not be complete (namely
;; if there were multiple opening brackets and some but not all have
;; been closed since the last call to `inform-syntax-class'), and rare
;; circumstances in which it might be wrong (namely if there are
;; multiple closing brackets and fewer, but at least two, opening
;; bracket since the last call). I consider these cases not worth
;; worrying about - and the speed hit of checking for them is
;; considerable.
(defun inform-syntax-class (&optional defun-start data)
"Return a list describing the syntax at point.
Optional argument DEFUN-START gives the point from which parsing
should start, and DATA is the list returned by a previous invocation
of `inform-syntax-class'. See code for details on the return type."
(let ((line-start (point))
in-obj state
(case-fold-search t))
(save-excursion
(cond (defun-start
(setq state (parse-partial-sexp defun-start line-start nil nil
(nth 3 data)))
(setq in-obj
(cond ((or (> (car state) 0) (nth 3 state) (nth 4 state))
(nth 1 data))
((nth 1 data) (/= (inform-preceding-char) ?\;))
(t (looking-at inform-object-regexp)))))
(t
(inform-beginning-of-defun)
(setq in-obj (looking-at inform-object-regexp)
state (parse-partial-sexp (point) line-start)))))
(list
(if (> (car state) 0)
;; If there's a containing sexp then it's easy.
(cond ((nth 3 state) 'string)
((nth 4 state) 'comment)
((looking-at (concat "\\s-*" comment-start)) 'comment)
((looking-at inform-label-regexp) 'label)
(t 'code))
;; Otherwise there are a bunch of special cases (has, with, class,
;; and private properties) that must be checked for. Note that
;; we have to distinguish between global class declarations and
;; class membership in an object declaration. This is done by
;; looking for a preceding semicolon.
(cond ((nth 3 state) 'string)
((nth 4 state) 'comment)
((looking-at (concat "\\s-*" comment-start)) 'comment)
((and in-obj (looking-at "\\s-*class\\>")
(/= (inform-preceding-char) ?\;))
'class)
((looking-at inform-action-regexp) 'action)
((looking-at inform-directive-regexp) 'directive)
((and (looking-at "\\[") (eq (inform-preceding-char) ?\;))
'directive)
((and (not in-obj) (eq (inform-preceding-char) ?\;))
'directive)
((looking-at "\\s-*$") 'blank)
((not in-obj) 'other)
((looking-at "\\s-*has\\(\\s-\\|$\\)") 'has)
((looking-at "\\s-*with\\(\\s-\\|$\\)") 'with)
((looking-at "\\s-*private\\(\\s-\\|$\\)") 'private)
((or (eq (inform-preceding-char) ?,)
(looking-at inform-property-regexp))
'property)
;; This handles declarations of objects in a class eg
;; Bird "swallow";
;; It assumes that class names follow the convention of being
;; capitalised. This is not the most elegant way of handling
;; this case but in practice works well.
((looking-at "\\s-*[A-Z]")
'directive)
(t
'other)))
;; Are we in an object?
(if (and in-obj
(not (looking-at inform-object-regexp))
(zerop (car state))
(eq (inform-preceding-char) ?\;))
nil
in-obj)
;; List of known enclosing sexps.
(let ((sexps (nth 2 data)) ; the old list of sexps
(depth (car state)) ; current nesting depth
(sexp-start (nth 1 state))) ; enclosing sexp, if any
(if sexps
;; Strip away closed sexps.
(let ((sexp-depth (car (car sexps))))
(while (and sexps (or (> sexp-depth depth)
(and (eq sexp-depth depth)
sexp-start)))
(setq sexps (cdr sexps)
sexp-depth (if sexps (car (car sexps)))))))
(if sexp-start
(setq sexps (cons (cons depth sexp-start) sexps)))
sexps)
;; State from the parse algorithm.
state)))
(defun inform-calculate-indentation (data)
"Return the correct indentation for the line at point.
DATA is the syntax class for the start of the line (as returned
by `inform-syntax-class'). It is assumed that point is somewhere
in the indentation for the current line (i.e., everything to the
left is whitespace)."
(let ((syntax (car data)) ; syntax class of start of line
(in-obj (nth 1 data)) ; inside an object?
(depth (car (nth 3 data))) ; depth of nesting of start of line
(case-fold-search t)) ; searches are case-insensitive
(cond
;; Directives should never be indented or else the directive-
;; finding code won't run fast enough. Hence the magic
;; constant 0.
((eq syntax 'directive) 0)
((eq syntax 'blank) 0)
;; Semicolons on a line of their own will be indented per the
;; current syntax unless user variable inform-indent-semicolon is
;; nil.
((and (looking-at "\\s-*;$") (not inform-indent-semicolon)) 0)
;; Various standard indentations.
((eq syntax 'property) inform-indent-property)
((eq syntax 'other)
(cond ((looking-at "\\s-*\\[") inform-indent-property)
(in-obj (+ inform-indent-property inform-indent-level))
(t inform-indent-level)))
((and (eq syntax 'string) (zerop depth))
(cond (in-obj (+ inform-indent-property inform-indent-level))
(t inform-indent-level)))
((and (eq syntax 'comment) (zerop depth))
(inform-line-up-comment
(if in-obj inform-indent-property 0)))
((eq syntax 'action) inform-indent-level)
((memq syntax '(has with class private)) inform-indent-has-with-class)
;; We are inside a sexp of some sort.
(t
(let ((indent 0) ; calculated indent column
paren ; where the enclosing sexp begins
string-start ; where string (if any) starts
(string-indent 0) ; indentation for the current str
cont-p ; true if line is a continuation
paren-char ; the parenthesis character
prec-token ; token preceding line
this-char) ; character under consideration
(save-excursion
;; Skip back to the start of a string, if any. (Note that
;; we can't be in a comment since the syntax class applies
;; to the start of the line.)
(if (eq syntax 'string)
(progn
(skip-syntax-backward "^\"")
(forward-char -1)
(setq string-start (point))
(setq string-indent (current-column))
))
;; Now find the start of the sexp containing point. Most
;; likely, the location was found by `inform-syntax-class';
;; if not, call `up-list' now and save the result in case
;; it's useful in future.
(save-excursion
(let ((sexps (nth 2 data)))
(if (and sexps (eq (car (car sexps)) depth))
(goto-char (cdr (car sexps)))
(up-list -1)
(setcar (nthcdr 2 data)
(cons (cons depth (point)) (nth 2 data)))))
(setq paren (point)
paren-char (following-char)))
;; If we were in a string, now skip back to the start of the
;; line. We have to do this *after* calling `up-list' just
;; in case there was an opening parenthesis on the line
;; including the start of the string.
(if (eq syntax 'string)
(forward-line 0))
;; The indentation depends on what kind of sexp we are in.
;; If line is in parentheses, indent to opening parenthesis.
(if (eq paren-char ?\()
(setq indent (progn (goto-char paren) (1+ (current-column))))
;; Line not in parentheses.
(setq prec-token (inform-preceding-token)
this-char (following-char))
(cond
;; Each 'else' should have the same indentation as the
;; matching 'if'
((looking-at "\\s-*else")
;; Find the matching 'if' by counting 'if's and 'else's
;; in this sexp
(let ((if-count 0) found)