1 ;; basic-mode.el --- A mode for editing Visual Basic programs.
3 ;; Copyright (C) 1996, Fred White <fwhite@world.std.com>
5 ;; Author: Fred White <fwhite@world.std.com>
6 ;; Version: 1.0 (April 18, 1996)
7 ;; Keywords: languages basic
10 ;; basic-mode|Fred White|fwhite@world.std.com|
11 ;; A mode for editing Visual Basic programs.|
12 ;; 18-Apr-96|1.0|~/modes/basic-mode.el.Z|
14 ;; This file is NOT part of GNU Emacs but the same permissions apply.
16 ;; GNU Emacs is free software; you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published
18 ;; by the Free Software Foundation; either version 2, or (at your
19 ;; option) any later version.
21 ;; GNU Emacs 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.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs; see the file COPYING. If not, write to the
28 ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
29 ;; This program is free software; you can redistribute it and/or
30 ;; modify it under the terms of the GNU General Public License as
31 ;; published by the Free Software Foundation; either version 2 of the
32 ;; License, or (at your option) any later version.
35 ;; Purpose of this package:
36 ;; This is a mode for editing programs written in The World's Most
37 ;; Successful Programming Language. It features automatic
38 ;; indentation, font locking, keyword capitalization, and some minor
39 ;; convenience functions.
41 ;; Installation instructions
42 ;; Put basic-mode.el somewhere in your path, compile it, and add the
43 ;; following to your init file:
45 ;; (autoload 'basic-mode "basic-mode" "Basic mode." t)
46 ;; (setq auto-mode-alist (append '(("\\.\\(frm\\|bas\\|cls\\)$" .
47 ;; basic-mode)) auto-mode-alist))
49 ;; Of course, under Windows 3.1, you'll have to name this file
50 ;; something shorter than basic-mode.el
55 ;; Doesn't know about ":" separated stmts
56 ;; Doesn't know about single-line IF stmts
60 ;; fwd/back-compound-statement
61 ;; completion over OCX methods and properties.
62 ;; ensure Then at the end of IF statements.
69 (defvar basic-xemacs-p (string-match "XEmacs\\|Lucid" (emacs-version)))
70 (defvar basic-winemacs-p (string-match "Win-Emacs" (emacs-version)))
72 ;; Variables you may want to customize.
73 (defvar basic-mode-indent 2 "*Default indentation per nesting level")
74 (defvar basic-fontify-p t "*Whether to fontify Basic buffers.")
75 (defvar basic-capitalize-keywords-p t
76 "*Whether to capitalize BASIC keywords.")
77 (defvar basic-wild-files "*.frm *.bas *.cls"
78 "*Wildcard pattern for BASIC source files")
79 (defvar basic-ide-pathname nil
80 "*The full pathname of your Visual Basic exe file, if any.")
83 (defvar basic-keywords-to-highlight
84 '("Dim" "If" "Then" "Else" "ElseIf" "End If")
85 "*A list of keywords to highlight in Basic mode, or T, meaning all keywords")
87 (defvar basic-defn-templates
88 (list "Public Sub ()\nEnd Sub\n\n"
89 "Public Function () As Variant\nEnd Function\n\n"
90 "Public Property Get ()\nEnd Property\n\n")
91 "*List of function templates though which basic-new-sub cycles.")
95 (defvar basic-mode-syntax-table nil)
96 (if basic-mode-syntax-table
98 (setq basic-mode-syntax-table (make-syntax-table))
99 (modify-syntax-entry ?\' "\<" basic-mode-syntax-table) ; Comment starter
100 (modify-syntax-entry ?\n ">" basic-mode-syntax-table)
101 (modify-syntax-entry ?\\ "w" basic-mode-syntax-table)
102 (modify-syntax-entry ?_ "w" basic-mode-syntax-table))
105 (defvar basic-mode-map nil)
108 (setq basic-mode-map (make-sparse-keymap))
109 (define-key basic-mode-map "\t" 'basic-indent-line)
110 (define-key basic-mode-map "\r" 'basic-newline-and-indent)
111 (define-key basic-mode-map "\M-\C-a" 'basic-beginning-of-defun)
112 (define-key basic-mode-map "\M-\C-e" 'basic-end-of-defun)
113 (define-key basic-mode-map "\M-\C-h" 'basic-mark-defun)
114 (define-key basic-mode-map "\M-\C-\\" 'basic-indent-region)
115 (define-key basic-mode-map "\M-q" 'basic-fill-or-indent)
119 (define-key basic-mode-map '(control C) 'basic-start-ide))
120 (define-key basic-mode-map "\M-G" 'basic-grep)
121 (define-key basic-mode-map '(meta backspace) 'backward-kill-word)
122 (define-key basic-mode-map '(control meta /) 'basic-new-sub))))
125 ;; These abbrevs are valid only in a code context.
126 (defvar basic-mode-abbrev-table nil)
128 (defvar basic-mode-hook ())
131 ;; Is there a way to case-fold all regexp matches?
133 (defconst basic-defun-start-regexp
135 "^[ \t]*\\([Pp]ublic \\|[Pp]rivate \\|[Ss]tatic \\)*"
136 "\\([Ss]ub\\|[Ff]unction\\|[Pp]roperty +[GgSsLl]et\\|[Tt]ype\\)"
137 "[ \t]+\\(\\w+\\)[ \t]*(?"))
139 (defconst basic-defun-end-regexp
140 "^[ \t]*[Ee]nd \\([Ss]ub\\|[Ff]unction\\|[Pp]roperty\\|[Tt]ype\\)")
143 ;; Includes the compile-time #if variation.
144 (defconst basic-if-regexp "^[ \t]*#?[Ii]f")
145 (defconst basic-else-regexp "^[ \t]*#?[Ee]lse\\([Ii]f\\)?")
146 (defconst basic-endif-regexp "[ \t]*#?[Ee]nd[ \t]*[Ii]f")
148 (defconst basic-continuation-regexp "^.*\\_[ \t]*$")
149 (defconst basic-label-regexp "^[ \t]*[a-zA-Z0-9_]+:$")
151 (defconst basic-select-regexp "^[ \t]*[Ss]elect[ \t]+[Cc]ase")
152 (defconst basic-case-regexp "^[ \t]*[Cc]ase")
153 (defconst basic-select-end-regexp "^[ \t]*[Ee]nd[ \t]+[Ss]elect")
155 (defconst basic-for-regexp "^[ \t]*[Ff]or")
156 (defconst basic-next-regexp "^[ \t]*[Nn]ext")
158 (defconst basic-do-regexp "^[ \t]*[Dd]o")
159 (defconst basic-loop-regexp "^[ \t]*[Ll]oop")
161 (defconst basic-while-regexp "^[ \t]*[Ww]hile")
162 (defconst basic-wend-regexp "^[ \t]*[Ww]end")
164 (defconst basic-with-regexp "^[ \t]*[Ww]ith")
165 (defconst basic-end-with-regexp "^[ \t]*[Ee]nd[ \t]+[Ww]ith")
167 (defconst basic-blank-regexp "^[ \t]*$")
168 (defconst basic-comment-regexp "^[ \t]*\\s<.*$")
171 ;; This is some approximation of the set of reserved words in Visual Basic.
172 (defconst basic-all-keywords
173 '("Aggregate" "And" "App" "AppActivate" "Application" "Array" "As"
174 "Asc" "AscB" "Atn" "Beep" "BeginTrans" "ByVal" "CBool" "CByte" "CCur"
175 "CDate" "CDbl" "CInt" "CLng" "CSng" "CStr" "CVErr" "CVar" "Call"
176 "Case" "ChDir" "ChDrive" "Character" "Choose" "Chr" "ChrB"
177 "ClassModule" "Clipboard" "Close" "Collection" "Column" "Columns"
178 "Command" "CommitTrans" "CompactDatabase" "Component" "Components"
179 "Const" "Container" "Containers" "Cos" "CreateDatabase" "CreateObject"
180 "CurDir" "Currency" "DBEngine" "DDB" "Data" "Database" "Databases"
181 "Date" "DateAdd" "DateDiff" "DatePart" "DateSerial" "DateValue" "Day"
182 "Debug" "Declare" "Deftype" "DeleteSetting" "Dim" "Dir" "Do" "Domain"
183 "Double" "Dynaset" "EOF" "Each" "Else" "End" "Environ" "Erase" "Err"
184 "Error" "Exit" "Exp" "FV" "False" "Field" "Fields" "FileAttr"
185 "FileCopy" "FileDateTime" "FileLen" "Fix" "Font" "For" "Form"
186 "FormTemplate" "Format" "Forms" "FreeFile" "FreeLocks" "Function"
187 "Get" "GetAllSettings" "GetAttr" "GetObject" "GetSetting" "GoSub"
188 "GoTo" "Group" "Groups" "Hex" "Hour" "IIf" "IMEStatus" "IPmt" "IRR"
189 "If" "InStr" "Input" "Int" "Integer" "Is" "IsArray" "IsDate" "IsEmpty"
190 "IsError" "IsMissing" "IsNull" "IsNumeric" "IsObject" "Kill" "LBound"
191 "LCase" "LOF" "LSet" "LTrim" "Left" "Len" "Let" "Like" "Line" "Load"
192 "LoadPicture" "LoadResData" "LoadResPicture" "LoadResString" "Loc"
193 "Lock" "Log" "Long" "Loop" "MDIForm" "MIRR" "Me" "MenuItems"
194 "MenuLine" "Mid" "Minute" "MkDir" "Month" "MsgBox" "NPV" "NPer" "Name"
195 "New" "Next" "Now" "Oct" "On" "Open" "OpenDatabase" "Operator"
196 "Option" "PPmt" "PV" "Parameter" "Parameters" "Partition" "Picture"
197 "Pmt" "Print" "Printer" "Printers" "Private" "ProjectTemplate"
198 "Properties" "Public" "Put" "QBColor" "QueryDef" "QueryDefs" "RGB"
199 "RSet" "RTrim" "Randomize" "Rate" "ReDim" "Recordset" "Recordsets"
200 "RegisterDatabase" "Relation" "Relations" "Rem" "RepairDatabase"
201 "Reset" "Resume" "Return" "Right" "RmDir" "Rnd" "Rollback" "RowBuffer"
202 "SLN" "SYD" "SavePicture" "SaveSetting" "Screen" "Second" "Seek"
203 "SelBookmarks" "Select" "SelectedComponents" "SendKeys" "Set"
204 "SetAttr" "SetDataAccessOption" "SetDefaultWorkspace" "Sgn" "Shell"
205 "Sin" "Single" "Snapshot" "Space" "Spc" "Sqr" "Static" "Stop" "Str"
206 "StrComp" "StrConv" "String" "Sub" "SubMenu" "Switch" "Tab" "Table"
207 "TableDef" "TableDefs" "Tan" "Then" "Time" "TimeSerial" "TimeValue"
208 "Timer" "To" "Trim" "True" "Type" "TypeName" "UBound" "UCase" "Unload"
209 "Unlock" "Val" "VarType" "Verb" "Weekday" "Wend"
210 "While" "Width" "With" "Workspace" "Workspaces" "Write" "Year"))
213 (defun basic-word-list-regexp (keys)
219 (setq re (concat re key (if keys "\\|" ""))))
220 (concat re "\\)\\b")))
222 (defun basic-keywords-to-highlight ()
223 (if (eq basic-keywords-to-highlight t)
225 basic-keywords-to-highlight))
228 (defvar basic-font-lock-keywords
230 ;; Names of functions.
231 (list basic-defun-start-regexp 3 'font-lock-function-name-face)
234 (cons basic-label-regexp 'font-lock-keyword-face)
237 ;; String-valued cases get font-lock-string-face regardless.
238 (list "^[ \t]*[Cc]ase[ \t]+\\([^'\n]+\\)" 1 'font-lock-keyword-face t)
240 ;; Any keywords you like.
241 (cons (basic-word-list-regexp (basic-keywords-to-highlight))
242 'font-lock-keyword-face)))
247 "A mode for editing Microsoft Visual Basic programs.
248 Features automatic indentation, font locking, keyword capitalization,
249 and some minor convenience functions.
253 (kill-all-local-variables)
254 (use-local-map basic-mode-map)
255 (setq major-mode 'basic-mode)
256 (setq mode-name "Basic")
257 (set-syntax-table basic-mode-syntax-table)
259 (add-hook 'write-file-hooks 'basic-untabify)
261 (setq local-abbrev-table basic-mode-abbrev-table)
262 (if basic-capitalize-keywords-p
264 (make-local-variable 'pre-abbrev-expand-hook)
265 (add-hook 'pre-abbrev-expand-hook 'basic-pre-abbrev-expand-hook)
269 (make-local-variable 'comment-start)
270 (setq comment-start "' ")
271 (make-local-variable 'comment-start-skip)
272 (setq comment-start-skip "'+ *")
273 (make-local-variable 'comment-column)
274 (setq comment-column 40)
275 (make-local-variable 'comment-end)
276 (setq comment-end "")
278 (make-local-variable 'indent-line-function)
279 (setq indent-line-function 'basic-indent-line)
281 (make-local-variable 'font-lock-keywords)
282 (setq font-lock-keywords basic-font-lock-keywords)
287 (run-hooks 'basic-mode-hook))
290 (defun basic-construct-keyword-abbrev-table ()
291 (if basic-mode-abbrev-table
293 (let ((words basic-all-keywords)
297 (setq word (car words)
299 (setq list (cons (list (downcase word) word) list)))
301 (define-abbrev-table 'basic-mode-abbrev-table list))))
303 (basic-construct-keyword-abbrev-table)
306 (defun basic-in-code-context-p ()
307 (if (fboundp 'buffer-syntactic-context) ; XEmacs function.
308 (null (buffer-syntactic-context))
309 ;; Attempt to simulate buffer-syntactic-context
310 ;; I don't know how reliable this is.
311 (let* ((beg (save-excursion
315 (parse-partial-sexp beg (point))))
316 (and (null (nth 3 list)) ; inside string.
317 (null (nth 4 list)))))) ; inside cocmment
319 (defun basic-pre-abbrev-expand-hook ()
320 ;; Allow our abbrevs only in a code context.
321 (setq local-abbrev-table
322 (if (basic-in-code-context-p)
323 basic-mode-abbrev-table)))
327 (defun basic-newline-and-indent (&optional count)
328 "Insert a newline, updating indentation."
332 (call-interactively 'newline-and-indent))
334 (defun basic-beginning-of-defun ()
336 (re-search-backward basic-defun-start-regexp))
338 (defun basic-end-of-defun ()
340 (re-search-forward basic-defun-end-regexp))
342 (defun basic-mark-defun ()
347 (basic-beginning-of-defun)
349 (zmacs-activate-region)))
351 (defun basic-indent-defun ()
355 (call-interactively 'basic-indent-region)))
358 (defun basic-fill-long-comment ()
359 "Fills block of comment lines around point."
360 ;; Derived from code in ilisp-ext.el.
364 (let ((comment-re "^[ \t]*\\s<+[ \t]*"))
365 (if (looking-at comment-re)
368 (progn (beginning-of-line) (point))
371 (while (and (not (bobp))
372 (looking-at basic-comment-regexp))
374 (if (not (bobp)) (forward-line 1))
376 (let ((start (point)))
378 ;; Make all the line prefixes the same.
379 (while (and (not (eobp))
380 (looking-at comment-re))
381 (replace-match fill-prefix)
387 ;; Fill using fill-prefix
388 (fill-region-as-paragraph start (point))))))))
391 (defun basic-fill-or-indent ()
392 "Fill long comment around point, if any, else indent current definition."
394 (cond ((save-excursion
396 (looking-at basic-comment-regexp))
397 (basic-fill-long-comment))
399 (basic-indent-defun))))
402 (defun basic-new-sub ()
403 "Insert template for a new subroutine. Repeat to cycle through alternatives."
406 (let ((templates (cons basic-blank-regexp
407 basic-defn-templates))
411 (setq tem (car templates)
412 templates (cdr templates))
413 (cond ((looking-at tem)
414 (replace-match (or (car templates)
416 (setq templates nil))))
418 (search-backward "()" bound t)))
421 (defun basic-untabify ()
422 "Do not allow any tabs into the file"
423 (if (eq major-mode 'basic-mode)
424 (untabify (point-min) (point-max)))
427 (defun basic-default-tag ()
428 (if (and (not (bobp))
437 (buffer-substring s e)))
439 (defun basic-grep (tag)
440 "Search BASIC source files in current directory for tag."
442 (list (let* ((def (basic-default-tag))
444 (format "Grep for [%s]: " def))))
445 (if (string= tag "") def tag))))
447 (grep (format "grep -n %s %s" tag basic-wild-files)))
451 (defun basic-start-ide ()
452 "Start Visual Basic (or your favorite IDE, (after Emacs, of course))
453 on the project file in the current directory.
454 Note: it's not a good idea to leave Visual Basic running while you
455 are editing in emacs, since Visual Basic has no provision for reloading
459 (cond ((not (fboundp 'win-exec))
460 (error "Not available"))
461 ((null basic-ide-pathname)
462 (error "No pathname set for Visual Basic. See basic-ide-pathname"))
463 ((setq file (car (directory-files (pwd) t "\\.vbp")))
465 (win-exec basic-ide-pathname 'win-show-normal file))
467 (error "No project file found.")))))
471 ;;; Indentation-related stuff.
473 (defun basic-indent-region (start end)
474 "Perform basic-indent-line on each line in region."
479 (while (and (not (eobp))
481 (if (not (looking-at basic-blank-regexp))
485 (cond ((fboundp 'zmacs-deactivate-region)
486 (zmacs-deactivate-region))
487 ((fboundp 'deactivate-mark)
492 (defun basic-previous-line-of-code ()
494 (forward-line -1)) ; previous-line depends on goal column
495 (while (and (not (bobp))
496 (or (looking-at basic-blank-regexp)
497 (looking-at basic-comment-regexp)))
501 (defun basic-find-original-statement ()
502 ;; If the current line is a continuation from the previous, move
503 ;; back to the original stmt.
504 (let ((here (point)))
505 (basic-previous-line-of-code)
506 (while (and (not (bobp))
507 (looking-at basic-continuation-regexp))
509 (basic-previous-line-of-code))
512 (defun basic-find-matching-stmt (open-regexp close-regexp)
513 ;; Searching backwards
515 (while (and (>= level 0) (not (bobp)))
516 (basic-previous-line-of-code)
517 (basic-find-original-statement)
518 (cond ((looking-at close-regexp)
519 (setq level (+ level 1)))
520 ((looking-at open-regexp)
521 (setq level (- level 1)))))))
523 (defun basic-find-matching-if ()
524 (basic-find-matching-stmt basic-if-regexp basic-endif-regexp))
526 (defun basic-find-matching-select ()
527 (basic-find-matching-stmt basic-select-regexp basic-select-end-regexp))
529 (defun basic-find-matching-for ()
530 (basic-find-matching-stmt basic-for-regexp basic-next-regexp))
532 (defun basic-find-matching-do ()
533 (basic-find-matching-stmt basic-do-regexp basic-loop-regexp))
535 (defun basic-find-matching-while ()
536 (basic-find-matching-stmt basic-while-regexp basic-wend-regexp))
538 (defun basic-find-matching-with ()
539 (basic-find-matching-stmt basic-with-regexp basic-end-with-regexp))
542 (defun basic-calculate-indent ()
543 (let ((original-point (point)))
546 ;; Some cases depend only on where we are now.
547 (cond ((or (looking-at basic-defun-start-regexp)
548 (looking-at basic-label-regexp)
549 (looking-at basic-defun-end-regexp))
552 ;; The outdenting stmts, which simply match their original.
553 ((or (looking-at basic-else-regexp)
554 (looking-at basic-endif-regexp))
555 (basic-find-matching-if)
556 (current-indentation))
558 ;; All the other matching pairs act alike.
559 ((looking-at basic-next-regexp) ; for/next
560 (basic-find-matching-for)
561 (current-indentation))
563 ((looking-at basic-loop-regexp) ; do/loop
564 (basic-find-matching-do)
565 (current-indentation))
567 ((looking-at basic-wend-regexp) ; while/wend
568 (basic-find-matching-while)
569 (current-indentation))
571 ((looking-at basic-with-regexp) ; with/end with
572 (basic-find-matching-with)
573 (current-indentation))
575 ((looking-at basic-select-end-regexp) ; select case/end select
576 (basic-find-matching-select)
577 (current-indentation))
579 ;; A case of a select is somewhat special.
580 ((looking-at basic-case-regexp)
581 (basic-find-matching-select)
582 (+ (current-indentation) basic-mode-indent))
585 ;; Other cases which depend on the previous line.
586 (basic-previous-line-of-code)
588 ;; Skip over label lines, which always have 0 indent.
589 (while (looking-at basic-label-regexp)
590 (basic-previous-line-of-code))
593 ((looking-at basic-continuation-regexp)
594 (basic-find-original-statement)
595 ;; Indent continuation line under matching open paren,
596 ;; or else one word in.
597 (let* ((orig-stmt (point))
601 (goto-char original-point)
604 ;; Only if point is now w/in cont. block.
605 (if (<= orig-stmt (point))
608 (cond (matching-open-paren
609 (1+ matching-open-paren))
611 ;; Else, after first word on original line.
612 (back-to-indentation)
614 (while (looking-at "[ \t]")
618 (basic-find-original-statement)
619 (let ((indent (current-indentation)))
620 ;; All the various +indent regexps.
621 (cond ((looking-at basic-defun-start-regexp)
622 (+ indent basic-mode-indent))
624 ((or (looking-at basic-if-regexp)
625 (looking-at basic-else-regexp))
626 (+ indent basic-mode-indent))
628 ((or (looking-at basic-select-regexp)
629 (looking-at basic-case-regexp))
630 (+ indent basic-mode-indent))
632 ((or (looking-at basic-do-regexp)
633 (looking-at basic-for-regexp)
634 (looking-at basic-while-regexp)
635 (looking-at basic-with-regexp))
636 (+ indent basic-mode-indent))
639 ;; By default, just copy indent from prev line.
642 (defun basic-indent-to-column (col)
643 (let* ((bol (save-excursion
647 (<= (point) (+ bol (current-indentation))))
651 (looking-at basic-blank-regexp))))
653 (cond ((/= col (current-indentation))
656 (back-to-indentation)
657 (delete-region bol (point))
660 ;; If point was in the whitespace, move back-to-indentation.
664 (back-to-indentation)))))
666 (defun basic-indent-line ()
667 "Indent current line for BASIC"
669 (basic-indent-to-column (basic-calculate-indent)))