;; Copyright 1993-2002, by the Cecil Project ;; Department of Computer Science and Engineering, University of Washington ;; See the $VORTEX_HOME/notes/LICENSE file for license information. ;;; diesel-mode.el --- major mode for editing Diesel code ;; Adapted from cecil-mode.el by Craig Chambers, May 2003. ;; ====================================================================== ;; user definable variables ;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv (if (and (string-match "XEmacs" emacs-version) (emacs-version>= 20)) (defconst diesel-emacs-features '("Temporary XEmacs 20 placeholder hack") "Temporary XEmacs 20 placeholder hack") (defconst diesel-emacs-features (let ((mse-spec 'no-dual-comments) (scanner 'v18) (interactive nil)) ;; vanilla GNU18/Epoch 4 uses default values (if (= 8 (length (parse-partial-sexp (point) (point)))) ;; we know we're using v19 style dual-comment specifications. ;; All Lemacsen use 8-bit modify-syntax-entry flags, as do all ;; patched FSF19, GNU18, Epoch4's. Only vanilla FSF19 uses ;; 1-bit flag. [GNU Emacs 19.19 uses 6 flag bits: 1234bp. -MDE] ;; Let's be as smart as we can about figuring this ;; out. (let ((table (copy-syntax-table))) (modify-syntax-entry ?a ". 12345678bp" table) (let* ((table-aref (aref table ?a)) ; a list in Emacs 19.34. (syntax-flags (lsh (if (listp table-aref) (car table-aref) table-aref) -16))) (cond ((= (logand syntax-flags 255) 255) (setq mse-spec '8-bit)) ((= syntax-flags 63) (setq mse-spec '6-bit)) (t (setq mse-spec '1-bit)))) ;; we also know we're using a quicker, built-in comment ;; scanner, but we don't know if its old-style or new. ;; Fortunately we can ask emacs directly (if (fboundp 'forward-comment) (setq scanner 'v19) (setq scanner 'old-v19)))) (if (string-match "XEmacs\\|Lucid" emacs-version) (setq interactive 'interactive-underscore)) ;; now cobble up the necessary list (list mse-spec scanner interactive)) "A list of needed features extant in the Emacs you are using. There are many flavors of Emacs out on the net, each with different features supporting those needed by diesel-mode. Here's the current known list, along with the values for this variable: Vanilla GNU 18/Epoch 4: (no-dual-comments v18) GNU 18/Epoch 4 (patch1): (8-bit old-v19) GNU 18/Epoch 4 (patch2): (8-bit v19) Lemacs 19.4 - 19.7: (8-bit old-v19) Lemacs 19.8 and over: (8-bit v19 interactive-underscore) FSF 19 [version?]: (1-bit v19) FSF 19 (patched) [ver?]: (8-bit v19) GNU 19.19 and up: (6-bit v19)")) (defvar diesel-mode-abbrev-table nil "Abbrev table in use in diesel-mode buffers.") (define-abbrev-table 'diesel-mode-abbrev-table ()) (defvar diesel-mode-map () "Keymap used in diesel-mode.") (if diesel-mode-map () (setq diesel-mode-map (make-sparse-keymap)) (define-key diesel-mode-map "\C-j" 'newline-and-indent) ;; (define-key diesel-mode-map "\C-m" 'newline-and-indent) (define-key diesel-mode-map "{" 'diesel-electric-brace) (define-key diesel-mode-map "}" 'diesel-electric-brace) ;; (define-key diesel-mode-map ";" 'diesel-electric-semi) (define-key diesel-mode-map "\t" 'diesel-indent-command) (define-key diesel-mode-map "\e\C-q" 'diesel-indent-exp) (define-key diesel-mode-map "\C-c\C-c" 'diesel-comment-region) (define-key diesel-mode-map "\C-c\C-u" 'diesel-uncomment-region) (define-key diesel-mode-map "\C-c\C-x" 'diesel-match-paren) ;; (define-key diesel-mode-map "\e\C-a" 'diesel-beginning-of-defun) ;; (define-key diesel-mode-map "\e\C-e" 'diesel-end-of-defun) (define-key diesel-mode-map "\e\C-x" 'diesel-indent-defun) (define-key diesel-mode-map "\177" 'diesel-electric-delete) (if (memq 'v18 diesel-emacs-features) (progn (define-key diesel-mode-map "\C-c'" 'diesel-tame-comments) (define-key diesel-mode-map "'" 'diesel-tame-insert) (define-key diesel-mode-map "[" 'diesel-tame-insert) (define-key diesel-mode-map "]" 'diesel-tame-insert) (define-key diesel-mode-map "(" 'diesel-tame-insert) (define-key diesel-mode-map ")" 'diesel-tame-insert))) ; (define-key diesel-mode-map "\C-c\C-v" 'diesel-version) ;; these are necessary because default forward-sexp and ;; backward-sexp don't automatically let-bind ;; parse-sexp-ignore-comments, which is needed for them to work ;; properly in a Diesel buffer. ;; (define-key diesel-mode-map "\e\C-f" 'diesel-forward-sexp) ;; (define-key diesel-mode-map "\e\C-b" 'diesel-backward-sexp) ) ;; Maybe there's a better way; this eliminates the need for key bindings ;; above and make more packages like reposition-window work with Diesel code. (defadvice beginning-of-defun (around diesel-bod (&optional arg) activate) "If in Diesel mode, call `diesel-beginning-of-defun' instead." (interactive "p") (if (eq major-mode 'diesel-mode) (diesel-beginning-of-defun arg) ad-do-it)) (defadvice end-of-defun (around diesel-eod (&optional arg) activate) "If in Diesel mode, call `diesel-end-of-defun' instead." (interactive "p") (if (eq major-mode 'diesel-mode) (diesel-end-of-defun arg) ad-do-it)) (defadvice forward-sexp (around diesel-forward-sexp (&optional arg) activate) "If in Diesel mode, call `diesel-forward-sexp' instead." (interactive "p") (if (eq major-mode 'diesel-mode) (diesel-forward-sexp arg) ad-do-it)) (defadvice backward-sexp (around diesel-backward-sexp (&optional arg) activate) "If in Diesel mode, call `diesel-backward-sexp' instead." (interactive "p") (if (eq major-mode 'diesel-mode) (diesel-backward-sexp arg) ad-do-it)) (defvar diesel-mode-syntax-table nil "Syntax table used in diesel-mode buffers.") (setq diesel-mode-syntax-table (make-syntax-table)) (modify-syntax-entry ?\\ "." diesel-mode-syntax-table) (modify-syntax-entry ?# "." diesel-mode-syntax-table) (modify-syntax-entry ?$ "." diesel-mode-syntax-table) (modify-syntax-entry ?^ "." diesel-mode-syntax-table) (modify-syntax-entry ?* "." diesel-mode-syntax-table) (modify-syntax-entry ?` "." diesel-mode-syntax-table) (modify-syntax-entry ?~ "." diesel-mode-syntax-table) (modify-syntax-entry ?+ "." diesel-mode-syntax-table) (modify-syntax-entry ?= "." diesel-mode-syntax-table) (modify-syntax-entry ?% "." diesel-mode-syntax-table) (modify-syntax-entry ?< "." diesel-mode-syntax-table) (modify-syntax-entry ?> "." diesel-mode-syntax-table) (modify-syntax-entry ?& "." diesel-mode-syntax-table) (modify-syntax-entry ?| "." diesel-mode-syntax-table) (modify-syntax-entry ?\' "." diesel-mode-syntax-table) ;; Comments are either "--" ... "\n" or "(--" ... "--)". (cond ((memq '8-bit diesel-emacs-features) ;; Lucid emacs has the best implementation ;; 12 = chars of 2-char comment start ;; 34 = chars of 2-char comment end ;; 56 = chars of 2-char comment start, style b ;; 78 = chars of 2-char comment end, style b ;; a or b = this 1-char comment end/begin refers to that comment style (modify-syntax-entry ?- ". 1267" diesel-mode-syntax-table) (modify-syntax-entry ?\n "> a" diesel-mode-syntax-table) (modify-syntax-entry ?\( "()5" diesel-mode-syntax-table) (modify-syntax-entry ?\) ")(8" diesel-mode-syntax-table)) ((memq '6-bit diesel-emacs-features) ;; Emacs 19; unfortunately, multiple comment syntaxes must start w/same char ;; 12 = chars of 2-char comment start ;; 34 = chars of 2-char comment end ;; b = comment style b (2nd char of comment start or 1st char of comment end) (modify-syntax-entry ?- ". 123" diesel-mode-syntax-table) (modify-syntax-entry ?\n ">" diesel-mode-syntax-table) ; comment end ;; (modify-syntax-entry ?\( "()1" diesel-mode-syntax-table) ;; Does this do the right thing? (modify-syntax-entry ?\) ")(4" diesel-mode-syntax-table)) ((memq '1-bit diesel-emacs-features) ;; FSF19 has sub-optimal, but workable implementation ;; Some strange behavior may be encountered. LOBBY FSF! (modify-syntax-entry ?- ". 124" diesel-mode-syntax-table) (modify-syntax-entry ?\n ">" diesel-mode-syntax-table)) (t ;; Vanilla GNU18 is just plain busted. We'll do the best we can, ;; but some strange behavior may be encountered. PATCH or UPGRADE! (modify-syntax-entry ?- ". 124" diesel-mode-syntax-table) (modify-syntax-entry ?\n ">" diesel-mode-syntax-table)) ) ;; For slightly more about these variables, see the diesel-mode documentation. (defvar diesel-indent-level 4 "*Indentation of Diesel statements with respect to containing block.") (defvar diesel-brace-imaginary-offset 0 "*Imagined indentation of a Diesel open brace that actually follows a statement.") (defvar diesel-brace-offset 0 "*Extra indentation for braces, compared with other text in same context.") (defvar diesel-argdecl-indent 5 "*Indentation level of declarations of Diesel function arguments.") (defvar diesel-continued-statement-offset 2 "*Extra indent for lines not starting new statements.") (defvar diesel-continued-brace-offset 0 "*Extra indent for substatements that start with open-braces. This is in addition to `diesel-continued-statement-offset'.") (defvar diesel-tab-always-indent t "*Controls the operation of the TAB key. If t (the default), always just indent the current line. If nil, indent the current line only if point is at the left margin or in the line's indentation; otherwise insert a tab. If not-nil-or-t, then tab is inserted only within literals (comments and strings) and inside preprocessor directives, but line is always reindented.") (defvar diesel-always-arglist-indent-p nil "*Control indentation of continued arglists. When non-nil, arglists continued on subsequent lines will always indent `diesel-empty-arglist-indent' spaces, otherwise, they will indent to just under previous line's argument indentation.") (defvar diesel-block-close-brace-offset 0 "*Extra indentation given to close braces which close a block. This variable can be either an integer or a list. If an integer, it describes the extra offset given a block closing brace (and a closing paren if `diesel-paren-as-block-close-p' is non-nil), treating all closing parens the same. If a list of the form (OTHERS . TOPLEVEL), OTHERS is an integer describing the offset given to all but top-level \(e.g. function\) closing braces, while TOPLEVEL is an integer describing offset given only to braces which close top-level constructs.") (defvar diesel-paren-as-block-close-p nil "*Treat a parenthesis which is the first non-whitespace on a line as a paren which closes a block. When non-nil, `diesel-indent-level' is subtracted, and `diesel-block-close-brace-offset' is added to the line's offset.") (defvar diesel-empty-arglist-indent (+ diesel-indent-level diesel-continued-statement-offset) "*Indicates how far to indent a line following an empty argument list. Nil means indent to just after the paren.") (defvar diesel-comment-only-line-offset 0 "*Indentation offset for line which contains only end-of-line style comments. This variable can take either a single integer or a list of integers. If a single integer this is the extra indentation offset to apply to all comment-only lines, except those which start in column zero. If a list is used, the first integer is for all non-column-zero comment-only lines and the second integer is for all column-zero lines. You can also use a list containing only 1 integer, in which case, this value is used for all comment-only lines. For example: value meaning ===== ======= 0 comment-only lines do not indent 4 non-col0 lines indent 4 spaces, col0 lines don't indent '(4) all comment-only lines indent 4 spaces '(4 1) non-col0 lines indent 4 spaces, col0 lines indent 1 space") (defvar diesel-block-comments-indent-p nil "*3 styles of Diesel block comments are supported. If this variable is nil, then styles 1-2 are supported. If this variable is non-nil, style 3 is supported. style 1: style 2: style 3: (-- (-- (-- blah -- blah blah blah -- blah blah --) --) --) ") (defvar diesel-relative-offset-p t "*Control the calculation for indentation. When non-nil (the default), indentation is calculated relative to the first statement in the block. When nil, the indentation is calculated without regard to how the first statement is indented.") (defvar diesel-untame-characters (and (memq 'v18 diesel-emacs-features) '(?\')) "*Utilize a backslashing workaround of an Emacs syntax parsing bug. If non-nil, this variable should contain a list of characters which will be prepended by a backslash in comment regions. By default, the list contains only the most troublesome character, the single quote. To be completely safe, set this variable to: '(?\( ?\) ?\' ?\{ ?\} ?\[ ?\]) This is the full list of characters which can potentially cause problems if they exist unbalanced within comments. Setting this variable to nil will defeat this feature, but be forewarned! Such un-escaped characters in comment regions can potentially break many things such as some indenting and blinking of parenthesis. Note further that only the default set of characters will be escaped automatically as they are typed. But, executing `diesel-tame-comments' \(\\[diesel-tame-comments]\) will escape all characters which are members of this set, and which are found in comments throughout the file. Finally, diesel-mode can tell if you're running a patched Emacs. If so, taming characters isn't necessary and this variable is automatically set to nil.") (defvar diesel-special-indent-hook nil "*Hook for user defined special indentation adjustments. This hook gets called after a line is indented by the mode. By supplying a hook, you can make adjustments to the line's standard indentation. If you do use this hook, you will likely need to also set `diesel-relative-offset-p' to nil. The call to this hook is wrapped in a `save-excursion' so you don't need to worry about restoring point and mark inside the hook function.") (defvar diesel-delete-function 'backward-delete-char-untabify "*Function called by `diesel-electric-delete' when deleting a single char.") (defvar diesel-backscan-limit 2000 "*Limit in characters for looking back while skipping syntactic ws. If you typically write really big methods, and start noticing incorrect indentations, try cranking this value up. The larger this value is, though, the slower parts of diesel-mode can become. Setting this variable to nil defeats backscan limits.") ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ;; NO USER DEFINABLE VARIABLES BEYOND THIS POINT ;; ;; ====================================================================== ;; diesel-mode main entry point ;; ====================================================================== (defun diesel-mode () "Major mode for editing Diesel code. 1. Expression and list commands understand all Diesel brackets, 2. Tab at left margin indents for Diesel code, 3. Both end-of-line and block comments are recognized, 4. Paragraphs are separated by blank lines only. IMPORTANT NOTE: You may notice that some characters (by default, only single quote) get escaped with a backslash when typed in a comment region, to work around a bug in GNU Emacs 18 and derivatives. Enter `\\[describe-variable] diesel-untame-characters RET' for more information. If you are running a patched Emacs, no characters will be escaped in comment regions, and many functions will run much faster. Key bindings: \\{diesel-mode-map} diesel-indent-level Indentation of Diesel statements within surrounding block. The surrounding block's indentation is the indentation of the line on which the open-brace appears. diesel-continued-statement-offset Extra indentation given to a substatement, such as the then-clause of an if or body of a while. diesel-continued-brace-offset Extra indentation given to a brace that starts a substatement. This is in addition to diesel-continued-statement-offset. diesel-brace-offset Extra indentation for line if it starts with an open brace. diesel-brace-imaginary-offset An open brace following other text is treated as if it were this far to the right of the start of its line. diesel-argdecl-indent Indentation level of declarations of Diesel function arguments. diesel-block-comments-indent-p Style of block comments to support. diesel-always-arglist-indent-p Control indentation of continued arglists. When non-nil, arglists continued on subsequent lines will always indent `diesel-empty-arglist-indent' spaces, otherwise, they will indent to just under previous line's argument indentation. diesel-backscan-limit Limit in characters for looking back while skipping syntactic whitespace. This variable is only used in an un-patched Emacs to help improve performance at the expense of some accuracy. Patched Emacses are both fast and accurate. diesel-block-close-brace-offset Extra indentation give to braces which close a block. diesel-comment-only-line-offset Extra indentation for a line containing only a comment. Can be an integer or list, specifying the various styles of comment-only line special indentations. diesel-delete-function Function called by `diesel-electric-delete' when deleting a single char. diesel-empty-arglist-indent Extra indentation to apply to a line following an empty argument list. nil means to line it up with the left paren. diesel-paren-as-block-close-p If non-nil, treat a parenthesis which is the first non-whitespace on a line as a paren which closes a block (i.e. treat it similar to right curly brace). diesel-relative-offset-p Control the calculation for indentation. When non-nil (the default), indentation is calculated relative to the first statement in the block. When nil, the indentation is calculated without regard to how the first statement is indented. Useful when using a `diesel-special-indent-hook'. diesel-special-indent-hook Hook for user defined special indentation adjustments. You can use this hook, which gets called after a line is indented by the mode, to customize indentations of the line. diesel-tab-always-indent Controls the operation of the TAB key. t means always just indent the current line. nil means indent the current line only if point is at the left margin or in the line's indentation; otherwise insert a tab. If not-nil-or-t, then tab is inserted only within literals (comments and strings) and inside preprocessor directives, but the line is always reindented. Default is t. diesel-untame-characters When non-nil, inserts backslash escapes before certain untamed characters in comment regions. It is recommended that you keep the default setting to workaround a nasty Emacs bug, unless you are running a patched Emacs. Turning on Diesel mode calls the value of the variable `diesel-mode-hook' with no args, if that value is non-nil." (interactive) (kill-all-local-variables) (use-local-map diesel-mode-map) (set-syntax-table diesel-mode-syntax-table) (setq major-mode 'diesel-mode mode-name "Diesel" local-abbrev-table diesel-mode-abbrev-table) (set (make-local-variable 'paragraph-start) (concat "^$\\|" page-delimiter)) (set (make-local-variable 'paragraph-separate) paragraph-start) (set (make-local-variable 'paragraph-ignore-fill-prefix) t) (make-local-variable 'fill-paragraph-function) (setq fill-paragraph-function 'diesel-fill-paragraph) ;; This has nothing to do with Diesel per se ;; (set (make-local-variable 'require-final-newline) t) (set (make-local-variable 'parse-sexp-ignore-comments) nil) ;; (set (make-local-variable 'indent-line-function) 'diesel-indent-line) (set (make-local-variable 'comment-start) "-- ") (set (make-local-variable 'comment-end) "") (set (make-local-variable 'comment-column) 32) (set (make-local-variable 'comment-start-skip) "(--+ *\\|--+ *") (set (make-local-variable 'comment-indent-hook) 'diesel-comment-indent) ;; I want my syntax highlighting! (make-local-variable 'font-lock-defaults) (setq font-lock-defaults '(diesel-font-lock-keywords nil t)) (run-hooks 'diesel-mode-hook)) ;; ====================================================================== ;; diesel syntax highlighting support ;; ====================================================================== (defvar diesel-font-lock-keywords (let ((kw1 (mapconcat 'identity '("let" "var" ; local decls "prim" "resend" "new" "shared" "private" "public" "protected" "module" "extends" "import" "friend" "end" "precedence" "below" "above" "with" "left_associative" "right_associative" "non_associative" "isa" "when" "disjoint" "cover" "by" "divide" "into" "forall" "where" ) "\\|")) (fn-like (mapconcat 'identity '("method" "signature" "fun" "field") "\\|")) (object-like (mapconcat 'identity '("object" "class" "predicate" "extend" "synonym") "\\|")) (object-kind (mapconcat 'identity '("abstract") "\\|")) (preprocessor-like (mapconcat 'identity '("include") "\\|")) ) (list ;; Highlighting actual keywords (list (concat "\\b\\(" kw1 "\\|" fn-like "\\|" object-like "\\)\\b[ \n\t;(]") 1 'font-lock-keyword-face) (list (concat "\\b\\(" object-kind "\\)\\b[ \n\t;]") 1 'font-lock-reference-face) (list (concat "\\b\\(" preprocessor-like "\\)\\b[ \n\t;]") 1 'font-lock-preprocessor-face) ;; Names of methods and method-like entities (list (concat "\\b\\(" fn-like "\\)[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)") 2 'font-lock-function-name-face) ;; Names of objects and object-like entities (list (concat "\\b\\(" object-like "\\)[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)") 2 'font-lock-type-face) ;; Variable/value names, fields, and parameter names '("\\blet[ \t]+\\(var[ \t]\\)*\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" 2 font-lock-variable-name-face) '("\\bfield[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" 1 font-lock-variable-name-face) '("\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)[:@]" 1 font-lock-variable-name-face) ;; Types in parameter lists etc. '("\\(@\\|:`?\\)\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" 2 font-lock-type-face) )) "Additional expressions to highlight in Diesel mode.") ;; ====================================================================== ;; diesel regular expressions ;; ====================================================================== (defconst diesel-ident-chars "_a-zA-Z!@#%\\^&*\\-=+\\\\~|<>/?0-9" "Character list for characters that can be in a Diesel identifier") (defconst diesel-ident (concat "[" diesel-ident-chars "]+") "Regexp to match a Diesel identifier") (defconst diesel-defun-header (concat "\\(^\\|^.*[ \t\n]\\)\\(" "method[ \t\n]+\\|" "fun[ \t\n]+\\|" "signature[ \t\n]+\\|" "field[ \t\n]+\\|" "class[ \t\n]+\\|" "object[ \t\n]+\\|" "predicate[ \t\n]+" "\\)") "Regexp to match beginning of method, field, or object/type declaration.") ;; Not all of these are used, but they give the logical structure of Diesel. ;; make-regexp.el is perhaps more efficient than diesel-re-make-alternates. (defun diesel-re-make-alternates (&rest alternates) (concat "\\(" (mapconcat (function (lambda (x) x)) alternates "\\|") "\\)")) (defconst diesel-privacy-nonoptional-re (diesel-re-make-alternates "public" "protected" "private")) (defconst diesel-privacy-re (concat diesel-privacy-nonoptional-re "?")) (defconst diesel-privacy+whitespace-re (concat (diesel-re-make-alternates "public[ \t\n]+" "protected[ \t\n]+" "private[ \t\n]+") "?")) (defconst diesel-rep-role-re (concat (diesel-re-make-alternates "abstract") "?")) (defconst diesel-rep-kind-re (diesel-re-make-alternates "class" "object" "predicate")) (defconst diesel-simple-decl-re (diesel-re-make-alternates "synonym" "fun" "signature" "method" "field" "module" "extend")) (defconst diesel-defun-header-sans-privacy (concat (diesel-re-make-alternates "fun\\b" "method\\b" "signature\\b" "field\\b" "class\\b" "object\\b" "predicate\\b" ;; ? module ;; ? extend (class|object)? (concat diesel-rep-role-re "[ \t]*" diesel-rep-kind-re "[ \t]*" ;; It's a tiny bit dangerous to use optional whitespace ;; instead of requiring it before alternatives besides ";". diesel-ident "[ \t\n]*" (diesel-re-make-alternates "isa" "when" ";")) )) "Regexp to match beginning of method, field, or object/type declaration.") (defconst diesel-defun-header (concat "^[ \t]*" diesel-privacy-re "[ \t]*" diesel-defun-header-sans-privacy)) ;; Sometimes the privacy part appears on a separate line. ;; It doesn't work to just use this, because re-search-backward (or forward, ;; with negative arg) finds the match that begins *latest* in the buffer. ;; This is only safe when searching only forward, as with `looking-at'. (defconst diesel-two-line-defun-header (concat "^[ \t]*" diesel-privacy+whitespace-re diesel-defun-header-sans-privacy)) ;; ====================================================================== ;; most command level (interactive) and related ;; ====================================================================== (defun diesel-tame-insert (arg) "Safely inserts certain troublesome characters in comment regions. Because of syntax bugs in Emacs, characters with string or parenthesis syntax must be escaped with a backslash or lots of things get messed up. Unfortunately, setting `parse-sexp-ignore-comments' to non-nil does not fix the problem, but this function is unnecessary if you are running a patched Emacs. See also the variable `diesel-untame-characters'." (interactive "p") (if (and (memq last-command-char diesel-untame-characters) (memq (diesel-in-literal) '(block to-end-of-line))) (insert "\\")) (self-insert-command arg)) (defun diesel-electric-delete (arg) "Just call `backward-delete-char-untabify'." (interactive "P") (funcall diesel-delete-function (prefix-numeric-value arg))) (defun diesel-electric-brace (arg) "Insert character, and correct indentation of current line." (interactive "P") (let (insertpos (last-command-char last-command-char) (bod (diesel-point 'bod))) (if (and (not arg) (save-excursion (skip-chars-forward " \t") (eolp)) (save-excursion (skip-chars-backward " \t") (bolp))) (progn (if (and (memq last-command-char diesel-untame-characters) (memq (diesel-in-literal bod) '(block to-end-of-line))) (insert "\\")) ;; we need to work around a bogus feature of Emacs where an ;; open brace at bolp means a beginning-of-defun. but it ;; really might not. (and (= last-command-char ?{) (bolp) (diesel-indent-line)) (insert last-command-char) (diesel-indent-line) (save-excursion (if insertpos (goto-char (1+ insertpos))) (delete-char -1)))) (if insertpos (save-excursion (goto-char insertpos) (self-insert-command (prefix-numeric-value arg))) (self-insert-command (prefix-numeric-value arg))))) (defun diesel-electric-semi (arg) "Insert character and correct line's indentation." (interactive "P") (if (diesel-in-literal) (self-insert-command (prefix-numeric-value arg)) (let ((here (point-marker))) (goto-char here) (set-marker here nil)) (diesel-electric-terminator arg))) (defun diesel-electric-terminator (arg) "Insert character and correct line's indentation." (interactive "P") (let (insertpos (end (point))) (if (and (not arg) (save-excursion (skip-chars-forward " \t") (eolp))) (progn (insert last-command-char) (diesel-indent-line) (save-excursion (if insertpos (goto-char (1+ insertpos))) (delete-char -1)))) (if insertpos (save-excursion (goto-char insertpos) (self-insert-command (prefix-numeric-value arg))) (self-insert-command (prefix-numeric-value arg))))) (defun diesel-indent-command (&optional whole-exp) "Indent current line as Diesel code, or in some cases insert a tab character. If `diesel-tab-always-indent' is t, always just indent the current line. If nil, indent the current line only if point is at the left margin or in the line's indentation; otherwise insert a tab. If not-nil-or-t, then tab is inserted only within literals (comments and strings) and inside preprocessor directives, but line is always reindented. A numeric argument, regardless of its value, means indent rigidly all the lines of the expression starting after point so that this line becomes properly indented. The relative indentation among the lines of the expression are preserved." (interactive "P") (let ((bod (diesel-point 'bod))) (if whole-exp ;; If arg, always indent this line as C ;; and shift remaining lines of expression the same amount. (let ((shift-amt (diesel-indent-line bod)) beg end) (save-excursion (if (eq diesel-tab-always-indent t) (beginning-of-line)) (setq beg (point)) (forward-sexp 1) (setq end (point)) (goto-char beg) (forward-line 1) (setq beg (point))) (if (> end beg) (indent-code-rigidly beg end shift-amt))) (cond ;; CASE 1: indent when at column zero or in lines indentation, ;; otherwise insert a tab ((not diesel-tab-always-indent) (if (and (save-excursion (skip-chars-backward " \t") (bolp)) (or (looking-at "[ \t]*$") (/= (point) (diesel-point 'boi)) (bolp))) (diesel-indent-line bod) (insert-tab))) ;; CASE 2: just indent the line ((eq diesel-tab-always-indent t) (diesel-indent-line bod)) ;; CASE 3: if in a literal, insert a tab, but always indent the line ((memq (diesel-in-literal bod) '(block to-end-of-line string)) (let ((here (point)) (boi (save-excursion (back-to-indentation) (point))) (indent-p nil)) (diesel-indent-line bod) (save-excursion (back-to-indentation) (setq indent-p (and (> here boi) (= (point) boi)))) (if indent-p (insert-tab)))) ;; CASE 4: bogus, just indent the line (t (diesel-indent-line bod)))))) (defun diesel-indent-exp () "Indent each line of the Diesel grouping following point." (interactive) (let ((indent-stack (list nil)) (contain-stack (list (point))) (case-fold-search nil) restart outer-loop-done inner-loop-done state ostate this-indent last-sexp last-depth at-else at-brace (parse-sexp-ignore-comments t) (opoint (point)) (next-depth 0)) (save-excursion (forward-sexp 1)) (save-excursion (setq outer-loop-done nil) (while (and (not (eobp)) (not outer-loop-done)) (setq last-depth next-depth) ;; Compute how depth changes over this line ;; plus enough other lines to get to one that ;; does not end inside a comment or string. ;; Meanwhile, do appropriate indentation on comment lines. (setq inner-loop-done nil) (while (and (not inner-loop-done) (not (and (eobp) (setq outer-loop-done t)))) (setq ostate state) ;; fix by reed@adapt.net.com ;; must pass in the return past the end of line, so that ;; parse-partial-sexp finds it, and recognizes that a "--" ;; comment is over. otherwise, state is set that we're in a ;; comment, and never gets unset, causing outer-loop to only ;; terminate in (eobp). old: ;;(setq state (parse-partial-sexp (point) ;;(progn (end-of-line) (point)) ;;nil nil state)) (let ((start (point)) (line-end (progn (end-of-line) (while (eq (diesel-in-literal) 'block) (forward-line 1) (diesel-indent-line) (end-of-line)) (skip-chars-backward " \t") (end-of-line) (point))) (end (progn (if (not (eobp)) (forward-char)) (point)))) (setq state (parse-partial-sexp start end nil nil state)) (goto-char line-end)) (setq next-depth (car state)) (if (and (car (cdr (cdr state))) (>= (car (cdr (cdr state))) 0)) (setq last-sexp (car (cdr (cdr state))))) (if (or (nth 4 ostate)) (diesel-indent-line)) (if (or (nth 3 state)) (forward-line 1) (setq inner-loop-done t))) (if (<= next-depth 0) (setq outer-loop-done t)) (if outer-loop-done nil ;; If this line had ..))) (((.. in it, pop out of the levels ;; that ended anywhere in this line, even if the final depth ;; doesn't indicate that they ended. (while (> last-depth (nth 6 state)) (setq indent-stack (cdr indent-stack) contain-stack (cdr contain-stack) last-depth (1- last-depth))) (if (/= last-depth next-depth) (setq last-sexp nil)) ;; Add levels for any parens that were started in this line. (while (< last-depth next-depth) (setq indent-stack (cons nil indent-stack) contain-stack (cons nil contain-stack) last-depth (1+ last-depth))) (if (null (car contain-stack)) (setcar contain-stack (or (car (cdr state)) (save-excursion (forward-sexp -1) (point))))) (forward-line 1) (skip-chars-forward " \t") ;; check for comment block (if (memq (diesel-in-literal) '(block)) (let ((eoc (save-excursion (re-search-forward "--)" (point-max) 'move) (point)))) (while (< (point) eoc) (diesel-indent-line) (forward-line 1)))) (if (eolp) nil (if (and (car indent-stack) (>= (car indent-stack) 0)) ;; Line is on an existing nesting level. ;; Lines inside parens are handled specially. (if (or (/= (char-after (car contain-stack)) ?{) ;;(diesel-at-top-level-p t)) ;; baw hack for continued statement offsets ;; repercussions??? t) (setq this-indent (car indent-stack)) ;; Line is at statement level. ;; Is it a new statement? Is it an else? ;; Find last non-comment character before this line (save-excursion (setq at-brace (= (following-char) ?{)) (diesel-backward-syntactic-ws opoint) (if (not (memq (preceding-char) '(nil ?\, ?\; ?} ?{))) ;; Preceding line did not end in comma or semi; ;; indent this line diesel-continued-statement-offset ;; more than previous. (progn (diesel-backward-to-start-of-continued-exp (car contain-stack)) (setq this-indent (+ diesel-continued-statement-offset (current-column) (if at-brace diesel-continued-brace-offset 0)))) ;; Preceding line ended in comma or semi; ;; use the standard indent for this level. (setq this-indent (car indent-stack))))) ;; Just started a new nesting level. ;; Compute the standard indent for this level. (let ((val (diesel-calculate-indent (if (car indent-stack) (- (car indent-stack)))))) (setcar indent-stack (setq this-indent val)))) ;; Adjust line indentation according to its contents (cond ;; looking at a comment only line? ((looking-at comment-start-skip) ;; different indentation base on whether this is a col0 ;; comment only line or not. also, if comment is in, or ;; to the right of comment-column, the comment doesn't ;; move (progn (skip-chars-forward " \t") (setq this-indent (if (>= (current-column) comment-column) (current-column) (diesel-comment-offset (bolp) (+ this-indent (if (save-excursion (diesel-backward-syntactic-ws (car contain-stack)) (memq (preceding-char) '(nil ?\, ?\; ?} ?{))) 0 diesel-continued-statement-offset)) ))))) ;; looking at a close brace ((= (following-char) ?}) (setq this-indent (- this-indent diesel-indent-level))) ;; looking at an open brace ((= (following-char) ?{) (setq this-indent (+ this-indent diesel-brace-offset (if (diesel-at-top-level-p t (car contain-stack)) 0 diesel-indent-level)))) ;; check for continued statements ((save-excursion (diesel-backward-syntactic-ws (car contain-stack)) (and (not (diesel-in-parens-p)) (not (memq (preceding-char) '(nil ?\000 ?\; ?\} ?\{))))) (setq this-indent (+ this-indent diesel-continued-statement-offset))) ) ;; end-cond ;; Put chosen indentation into effect. (or (= (current-column) this-indent) (progn (delete-region (point) (progn (beginning-of-line) (point))) (indent-to this-indent))) ;; Indent any comment following the text. (or (looking-at comment-start-skip) (if (re-search-forward comment-start-skip (diesel-point 'eol) t) (progn (indent-for-comment) (beginning-of-line)))) )))))) (defun diesel-comment-indent () "Used by `indent-for-comment' to decide how much to indent a comment in Diesel code based on its context." (if (looking-at "^\\(--\\|(--\\)") 0 ; Existing comment at bol stays there. (save-excursion (skip-chars-backward " \t") (max ;; leave at least one space on non-empty lines. (if (zerop (current-column)) 0 (1+ (current-column))) ;; use comment-column if previous line is comment only line ;; indented to the left of comment-column (save-excursion (beginning-of-line) (if (not (bobp)) (forward-line -1)) (skip-chars-forward " \t") (if (looking-at "--\\|(--") (if (< (current-column) comment-column) comment-column (current-column)) 0)) (let ((cur-pt (point))) (beginning-of-line 0) ;; If previous line had a comment, use it's indent (if (re-search-forward comment-start-skip cur-pt t) (progn (goto-char (match-beginning 0)) (current-column)) comment-column)))))) ; otherwise indent at comment column. (defun diesel-tame-comments () "Backslashify all untamed in comment regions found in the buffer. This is a workaround for Emacs syntax bugs. This function is unnecessary (and un-used automatically) if you are running a patched Emacs. Untamed characters to escape are defined in the variable `diesel-untame-characters'." (interactive) ;; make the list into a valid charset, escaping where necessary (let ((charset (concat "^" (mapconcat (function (lambda (char) (if (memq char '(?\\ ?^ ?-)) (concat "\\" (char-to-string char)) (char-to-string char)))) diesel-untame-characters "")))) (save-excursion (beginning-of-buffer) (while (not (eobp)) (skip-chars-forward charset) (if (and (not (zerop (following-char))) (memq (diesel-in-literal) '(block to-end-of-line)) (/= (preceding-char) ?\\ )) (insert "\\")) (if (not (eobp)) (forward-char 1)))))) ;; taken from match-paren.el. Author: unknown (defun diesel-match-paren () "Jump to the paren matching the one under point, if there is one." (interactive) (let ((parse-sexp-ignore-comments (memq 'v19 diesel-emacs-features))) (cond ((looking-at "[\(\[{]") (forward-sexp 1) (backward-char)) ((looking-at "[])}]") (forward-char) (backward-sexp 1)) (t (message "Could not find matching paren."))))) (if (memq 'interactive-underscore diesel-emacs-features) (defun diesel-forward-sexp (&optional arg) "Safe forward-sexp call." (interactive "_p") (diesel-forward-sexp-internal arg)) (defun diesel-forward-sexp (&optional arg) "Safe forward-sexp call." (interactive "p") (diesel-forward-sexp-internal arg))) (defun diesel-forward-sexp-internal (arg) (if (not arg) (setq arg 1)) (let ((parse-sexp-ignore-comments (memq 'v19 diesel-emacs-features))) ;; If Diesel mode advises forward-sexp, there's an infinite recursion. ;; So don't call forward-sexp, but inline the Emacs 19.34 definition. ;; (forward-sexp arg) (goto-char (or (scan-sexps (point) arg) (buffer-end arg))) (if (< arg 0) (backward-prefix-chars)))) (if (memq 'interactive-underscore diesel-emacs-features) (defun diesel-backward-sexp (&optional arg) "Safe backward-sexp call." (interactive "_p") (diesel-forward-sexp (- (or arg 1)))) (defun diesel-backward-sexp (&optional arg) "Safe backward-sexp call." (interactive "p") (diesel-forward-sexp (- (or arg 1))))) ;; ====================================================================== ;; compatibility between emacsen ;; ====================================================================== (defun diesel-backward-syntactic-ws (&optional lim) "Skip backwards over syntactic whitespace. Syntactic whitespace is defined as lexical whitespace and comments. Search no farther back than optional LIM. If LIM is omitted, `beginning-of-defun' is used." (let ((lim (or lim (diesel-point 'bod))) literal stop) (if (and diesel-backscan-limit (> (- (point) lim) diesel-backscan-limit)) (setq lim (- (point) diesel-backscan-limit))) (while (not stop) (skip-chars-backward " \t\n\r\f" lim) (setq literal (diesel-in-literal lim)) (if (eq literal 'to-end-of-line) ;; in -- comment (progn (skip-chars-backward "^-" lim) (skip-chars-backward "-" lim) (while (not (or (and (= (following-char) ?-) (= (char-after (1+ (point))) ?-)) (<= (point) lim))) (skip-chars-backward "^-" lim) (skip-chars-backward "-" lim))) (if (eq literal 'block) ;; in \(-- comment (progn (skip-chars-backward "^-" lim) (skip-chars-backward "-" lim) (while (not (or (and (= (following-char) ?-) (= (preceding-char) ?\()) (<= (point) lim))) (skip-chars-backward "^-" lim) (skip-chars-backward "-" lim)) (or (bobp) (forward-char -1)))) (if (and (= (preceding-char) ?\)) (= (char-after (- (point) 2)) ?-) (= (char-after (- (point) 3)) ?-)) ;; just after (-- --) block (progn (skip-chars-backward "^-" lim) (skip-chars-backward "-" lim) (while (not (or (and (= (following-char) ?-) (= (preceding-char) ?\()) (<= (point) lim))) (skip-chars-backward "^-" lim) (skip-chars-backward "-" lim)) (or (bobp) (forward-char -1))) ;; none of the above (setq stop t)))))) (defun diesel-skip-whitespace () ;; Leave point before the next token, skipping white space and comments. (while (progn (skip-chars-forward " \t\r\n\f") (looking-at "(?--")) (cond ((looking-at "--") (forward-line 1)) ((looking-at "(--") (search-forward "--)" nil 'move))))) (defun diesel-in-literal (&optional lim) "Determine if point is in a Diesel ``literal''. Return `block' if in a \(-- comment, `to-end-of-line' if in a -- comment, `string' if in a string literal, or nil if not in a comment at all. Optional LIM is used as the backward limit of the search. If omitted, or nil, `diesel-beginning-of-defun' is used." (save-excursion (let* ((here (point)) (state nil) (match nil) (backlim (or lim (diesel-point 'bod)))) (goto-char backlim) (while (< (point) here) (setq match (and (re-search-forward "\\(--\\|(--\\)\\|[\"']" here 'move) (buffer-substring (match-beginning 0) (match-end 0)))) (setq state (cond ;; no match ((null match) nil) ;; looking at the opening of a -- comment ((string= "--" match) (if (<= here (progn (end-of-line) (point))) 'to-end-of-line)) ;; looking at the opening of a (-- comment ((string= "(--" match) (if (not (re-search-forward "--)" here 'move)) 'block)) ;; looking at the opening of a double quote string ((string= "\"" match) (if (not (save-restriction ;; this seems to be necessary since the ;; re-search-forward will not work without it (narrow-to-region (point) here) (re-search-forward ;; this regexp matches a double quote ;; which is preceded by an even number ;; of backslashes, including zero "\\([^\\]\\|^\\)\\(\\\\\\\\\\)*\"" here 'move))) 'string)) ;; looking at the opening of a single quote string ((string= "'" match) (if (not (save-restriction ;; see comments from above (narrow-to-region (point) here) (re-search-forward ;; this matches a single quote which is ;; preceded by zero or two backslashes. "\\([^\\]\\|^\\)\\(\\\\\\\\\\)?'" here 'move))) 'string)) (t nil))) ) ; end-while ;; (message "in literal: %s" state) (sit-for 3) state))) ;; ====================================================================== ;; defuns for parsing syntactic elements ;; ====================================================================== (defun diesel-parse-state (&optional limit) "Determinate the syntactic state of the code at point. Iteratively uses `parse-partial-sexp' from point to LIMIT and returns the result of `parse-partial-sexp' at point. LIMIT is optional and defaults to `point-max'." (let ((limit (or limit (point-max))) (parse-sexp-ignore-comments t) state) (while (< (point) limit) (setq state (parse-partial-sexp (point) limit 0)) ;; (message "state: %s" state) (sit-for 3) ) state)) (defun diesel-at-top-level-p (wrt &optional bod) "Return t if point is not inside a containing Diesel expression, nil if it is embedded in an expression. When WRT is non-nil, returns nil if not at the top level with respect to an enclosing class, or the depth of class nesting at point. With WRT nil, returns nil if not at the ``real'' top level. Optional BOD is the beginning of defun." (save-excursion (let ((indent-point (point)) (case-fold-search nil) state containing-sexp-begin paren-depth (bod (or bod (diesel-point 'bod))) foundp) (goto-char bod) (setq state (diesel-parse-state indent-point) containing-sexp-begin (nth 1 state) paren-depth (nth 0 state)) (cond ((not wrt) (null containing-sexp-begin)) ((null containing-sexp-begin) 0) ((diesel-in-parens-p) nil) (t ;; calculate depth wrt containing (possibly nested) classes (goto-char containing-sexp-begin) (while (and (setq foundp (re-search-backward "[;}]" (point-min) t)) (let ((bod (diesel-point 'bod))) (or (diesel-in-literal bod) (diesel-in-parens-p bod))))) (if (memq (following-char) '(?} ?\;)) nil (setq state (diesel-parse-state containing-sexp-begin)) (and foundp (not (nth 1 state)) (nth 2 state) paren-depth)) ))))) (defun diesel-in-parens-p (&optional lim) "Return t if inside a paren expression. Optional LIM is used as the backward limit of the search." (let ((lim (or lim (diesel-point 'bod)))) (condition-case var (save-excursion (save-restriction (narrow-to-region (point) lim) (goto-char (point-max)) (= (char-after (or (scan-lists (point) -1 1) (point-min))) ?\())) (error nil) ))) (defun diesel-in-function-p (&optional containing) "Return t if inside a Diesel function definition. Optional CONTAINING is position of containing s-exp open brace. If not supplied, point is used as search start." (save-excursion (let ((here (if (not containing) (point) (goto-char containing) (diesel-backward-syntactic-ws) (point)))) (goto-char here) (= (preceding-char) ?\))))) ;; ====================================================================== ;; defuns for calculating indentation ;; ====================================================================== (defun diesel-indent-line (&optional bod) "Indent current line as Diesel code. Return the amount the indentation changed by. Optional BOD is the point of the beginning of the Diesel definition." (let* ((bod (or bod (diesel-point 'bod))) (indent (diesel-calculate-indent nil bod)) beg shift-amt close-paren top-close-paren (case-fold-search nil) (pos (- (point-max) (point)))) ;; calculate block close paren offset ;; (message "Final indent: %d" indent) (sit-for 2) (if (listp diesel-block-close-brace-offset) (setq close-paren (car diesel-block-close-brace-offset) top-close-paren (cdr diesel-block-close-brace-offset)) (setq close-paren diesel-block-close-brace-offset top-close-paren diesel-block-close-brace-offset)) ;; now start cleanup (beginning-of-line) (setq beg (point)) (cond ((eq indent nil) (setq indent (current-indentation))) ((eq indent t) (setq indent (diesel-calculate-indent-within-block-comment))) ((save-excursion (back-to-indentation) (looking-at "--\\|(--")) ;; we've found a comment-only line. we now must try to determine ;; if the line is a continuation from a comment on the previous ;; line. we check to see if the comment starts in or to the ;; right of comment-column and if so, we don't change its ;; indentation. (skip-chars-forward " \t") (if (>= (current-column) comment-column) (setq indent (current-column)) (setq indent (diesel-comment-offset (bolp) indent)))) (t (skip-chars-forward " \t") (if (listp indent) (setq indent (car indent))) (cond ((and (= (following-char) ?\)) diesel-paren-as-block-close-p) (setq indent (+ (- indent diesel-indent-level) (if (save-excursion (forward-char 1) (diesel-at-top-level-p nil bod)) top-close-paren close-paren)))) ((= (following-char) ?}) (setq indent (+ (- indent diesel-indent-level) (if (save-excursion (forward-char 1) (diesel-at-top-level-p nil bod)) top-close-paren close-paren)))) ((= (following-char) ?{) (setq indent (+ indent diesel-brace-offset)))))) (skip-chars-forward " \t") (setq shift-amt (- indent (current-column))) (if (zerop shift-amt) (if (> (- (point-max) pos) (point)) (goto-char (- (point-max) pos))) (delete-region beg (point)) (indent-to indent) ;; If initial point was within line's indentation, ;; position after the indentation. Else stay at same point in text. (if (> (- (point-max) pos) (point)) (goto-char (- (point-max) pos)))) ;; save-excursion is necessary because things break if the hook ;; changes point or mark (save-excursion (run-hooks 'diesel-special-indent-hook)) shift-amt)) (defun diesel-cont-indent (ipnt char lim) "Calculate the indentation for a continued statement. IPNT is the indentation point; CHAR is the character before the indentation point, excluding any intervening whitespace; LIM is the minimum point to search backwards to." (let ((charlist '(nil ?\000 ?\, ?\; ?\} ?\{)) streamop-pos here) (goto-char ipnt) (diesel-backward-syntactic-ws lim) (if (not (memq char charlist)) ;; This line is continuation of preceding line's statement (progn (diesel-backward-to-start-of-continued-exp lim) (setq here (point)) (+ (current-column) ;; prevent repeated continued indentation (if (save-excursion (beginning-of-line 1) (diesel-backward-syntactic-ws lim) (memq (preceding-char) charlist)) diesel-continued-statement-offset (progn (beginning-of-line 1) (forward-char -1) (diesel-backward-syntactic-ws lim) (diesel-backward-to-start-of-continued-exp lim) 0)) (save-excursion (goto-char ipnt) (skip-chars-forward " \t") (cond ((= (following-char) ?\{) diesel-continued-brace-offset) ((and (= (following-char) ?\}) (progn (forward-char 1) (diesel-at-top-level-p nil lim))) (- diesel-continued-statement-offset)) (t 0))))) nil))) (defun diesel-calculate-indent (&optional parse-start bod) "Return appropriate indentation for current line as Diesel code. In usual case returns an integer: the column to indent to. Returns nil if line starts inside a string, t if in a comment. Optional PARSE-START is the location to start parsing, and optional BOD is the beginning of the Diesel definition." (save-excursion (beginning-of-line) (let ((indent-point (point)) (case-fold-search nil) state do-indentation literal containing-sexp-begin streamop-pos char-before-ip (inclass-shift 0) inclass-depth inclass-unshift (bod (or bod (diesel-point 'bod)))) (if parse-start (goto-char parse-start) (goto-char bod)) (setq parse-start (point) state (diesel-parse-state indent-point) containing-sexp-begin (nth 1 state)) (if (null state) (let* ((bod (diesel-point 'bod))) (goto-char bod) (setq state (diesel-parse-state indent-point) containing-sexp-begin (nth 1 state) parse-start (point)))) (setq literal (diesel-in-literal bod)) ;; (message "in literal: %s" literal) (sit-for 3) ;; cache char before indent point (save-excursion (goto-char indent-point) (diesel-backward-syntactic-ws bod) (setq char-before-ip (preceding-char))) (cond ;; CASE 1: in a string. ((memq literal '(string)) nil) ;; CASE 2: in a comment. ((memq literal '(block to-end-of-line)) t) ;; CASE 3: Line is at top level. May be comment-only line, ;; data or function definition, or may be function argument ;; declaration. Indent like the previous top level line. ((setq inclass-depth (diesel-at-top-level-p t bod)) (+ ;; add an offset if we are inside a class defun body, i.e. we ;; are at the top level, but only wrt a containing class (let ((shift/level (+ diesel-indent-level diesel-brace-imaginary-offset))) (setq inclass-shift (* shift/level inclass-depth) inclass-unshift (* shift/level (max 0 (1- inclass-depth)))) inclass-shift) (progn (goto-char indent-point) (skip-chars-forward " \t") (cond ;; ((or (= (following-char) ?{) (progn (diesel-backward-syntactic-ws parse-start) (bobp))) 0) ;; first arg decl ((diesel-in-function-p) (goto-char indent-point) (skip-chars-forward " \t") diesel-argdecl-indent) ;; ((or (= (preceding-char) ?}) (= (preceding-char) ?\))) ;; indentation of class defun opening brace may not be ;; zero (goto-char (or containing-sexp-begin bod)) (- (current-indentation) ;; remove some nested inclass indentation inclass-unshift)) ;; ((progn (beginning-of-line) (skip-chars-forward " \t") (or (memq (diesel-in-literal bod) '(block to-end-of-line)) (looking-at "--\\|(--"))) 0) ;; we might be looking at the opening brace of a class ;; defun ((= (following-char) ?\{) ;; indentation of opening brace may not be zero (- (current-indentation) ;; remove some nested inclass indentation inclass-unshift)) ((eolp) ;; looking at a blank line, indent next line to zero 0) ;; at beginning of buffer, if nothing else, indent to zero ((save-excursion (goto-char indent-point) (beginning-of-line) (bobp)) 0) ;; this could be a compound statement, but make sure its ;; not a member init list ((save-excursion (goto-char indent-point) (diesel-backward-syntactic-ws bod) (and (= (preceding-char) ?,) (save-excursion (while (and (< bod (point)) (= (preceding-char) ?,)) (beginning-of-line) (diesel-backward-syntactic-ws bod)) (forward-line 1) t))) diesel-continued-statement-offset) (t (if (diesel-in-parens-p) ;; we are perhaps inside a member init call (while (and (diesel-in-parens-p) (< bod (point))) (forward-line -1) (skip-chars-forward " \t"))) ;; skip to start of compound statement (let ((ipnt (point))) (diesel-backward-syntactic-ws bod) (while (and (= (preceding-char) ?,) (< bod (point))) (beginning-of-line) (skip-chars-forward " \t") (setq ipnt (point)) (diesel-backward-syntactic-ws bod)) (goto-char ipnt)) ;; subtract inclass-shift since its already incorporated ;; by default in current-column (- (current-column) inclass-shift) ))))) ;; CASE 4: line is expression, not statement. indent to just ;; after the surrounding open -- unless empty arg list, in ;; which case we do what diesel-empty-arglist-indent says to do. ((/= (char-after containing-sexp-begin) ?{) (if (and diesel-empty-arglist-indent (or diesel-always-arglist-indent-p (null (nth 2 state)) ;; indicates empty arg list. Use a heuristic: if ;; the first non-whitespace following left paren ;; on same line is not a comment, is not an empty ;; arglist. (save-excursion (goto-char (1+ containing-sexp-begin)) (looking-at "[ \t]*[/\n]")))) (progn (goto-char containing-sexp-begin) (beginning-of-line) (skip-chars-forward " \t") (goto-char (min (+ (point) diesel-empty-arglist-indent) (1+ containing-sexp-begin))) (current-column)) (goto-char (1+ containing-sexp-begin)) ;; we want to skip any whitespace b/w open paren and ;; first argument. this handles while (thing) style ;; and while( thing ) style (skip-chars-forward " \t") (current-column))) ;; CASE 5: Statement. Find previous non-comment character. (t ;; (message "a statement") (sit-for 2) (or (diesel-cont-indent indent-point char-before-ip containing-sexp-begin) ;; This line is the start of a new statement. ;; Position following last unclosed open. (progn (goto-char containing-sexp-begin) ;; Is line first statement after an open-brace? ;; (message "containing sexpr place") (sit-for 2) (or (and diesel-relative-offset-p ;; If no, find that first statement and ;; indent like it. (save-excursion (forward-char 1) ;; Skip over comments following openbrace. (diesel-skip-whitespace) ;; The first following code counts ;; if it is before the line we want to indent. (and (< (point) indent-point) (+ (current-column) (diesel-compound-offset char-before-ip containing-sexp-begin bod))))) ;; If no previous statement, indent it relative to ;; line brace is on. For open brace in column ;; zero, don't let statement start there too. If ;; diesel-indent-level is zero, use diesel-brace-offset + ;; diesel-continued-statement-offset instead. For ;; open-braces not the first thing in a line, add ;; in diesel-brace-imaginary-offset. (+ (if (and (bolp) (zerop diesel-indent-level)) (+ diesel-brace-offset diesel-continued-statement-offset) diesel-indent-level) ;; Move back over whitespace before the openbrace. ;; If openbrace is not first nonwhite thing on the line, ;; add the diesel-brace-imaginary-offset. (progn (skip-chars-backward " \t") (if (bolp) 0 diesel-brace-imaginary-offset)) ;; We need to figure out what's before the openbrace. ;; It might be the return type of a closure or method, ;; or it might be the arg list of same, or it might be ;; whatever is before the closure expression. Try to ;; distinguish these cases, and if the openbrace follows ;; a method or closure decl, use indentation from that line. (progn (let ((c (preceding-char))) (cond ((eq c ?\]) ;; openbrace follows a parameterized return type. ;; skip over params, then type name (forward-sexp -2) (diesel-backward-syntactic-ws bod) (if (eq (preceding-char) ?:) ;; found colon; back up some more (progn (forward-char -1) (diesel-backward-syntactic-ws bod) (if (eq (preceding-char) ?\)) ;; found arg list; skip over them (forward-sexp -1))))) ((eq c ?\)) ;; found arg list; skip over them (forward-sexp -1)) (t ;; found some other character; check to see if it's a ;; return type (let ((here (point))) (if (/= (skip-chars-backward diesel-ident-chars) 0) ;; skipped over an identifier ;; found an identifier; might be a return type (progn (diesel-backward-syntactic-ws bod) (if (eq (preceding-char) ?:) ;; found colon; is a return type; ;; back up some more (progn (forward-char -1) (diesel-backward-syntactic-ws bod) (if (eq (preceding-char) ?\)) ;; found arg list; skip over them (forward-sexp -1))) ;; not a return type; go back (goto-char here))) ;; not an ident; skip forwards again (goto-char here)))) )) ;; Get initial indentation of the line we are on. (current-indentation))))))) ; end t outer cond )))) (defun diesel-calculate-indent-within-block-comment () "Return the indentation amount for line, assuming that the current line is to be regarded as part of a block comment." (let (end hyphens indent) (save-excursion (beginning-of-line) (skip-chars-forward " \t") (setq hyphens (looking-at "--")) (skip-chars-backward " \t\n") (setq end (point)) (beginning-of-line) (skip-chars-forward " \t") (if (re-search-forward "(--[ \t]*" end t) (goto-char (+ (match-beginning 0) (cond (diesel-block-comments-indent-p 0) (hyphens 1) (t (- (match-end 0) (match-beginning 0))))))) (current-column)))) (defun diesel-comment-offset (col0-line-p indent) "Calculate and return the comment-only line offset. Offset is based on the value of `diesel-comment-only-line-offset', the argument COL0-LINE-P, and the current indentation INDENT." (let ((offset 0)) (if col0-line-p ;; col0 means we need to look at the second member of the var's ;; list value. if value is not a list, then zero is used (if (listp diesel-comment-only-line-offset) ;; it is a list, so second element must be nil or a number (setq offset (+ indent (or (car (cdr diesel-comment-only-line-offset)) (car diesel-comment-only-line-offset))))) ;; not in column zero so indentation is car or value of variable (setq offset (+ indent (if (listp diesel-comment-only-line-offset) (car diesel-comment-only-line-offset) diesel-comment-only-line-offset)))) offset)) (defun diesel-compound-offset (char-before-ip containing-sexp-begin bod) "Calculate any addition offset due a comma separated compound statement. CHAR-BEFORE-IP is the character before the indentation point and CONTAINING-SEXP-BEGIN is the buffer position of the open brace or paren. BOD is the `beginning-of-defun' point." (cond ;; not a compound statement ((/= char-before-ip ?,) 0) ;; otherwise (t diesel-continued-statement-offset) )) ;; ====================================================================== ;; defuns to look backwards for things ;; ====================================================================== (defun diesel-backward-to-start-of-continued-exp (lim) (if (memq (preceding-char) '(?\) ?\")) (forward-sexp -1)) (beginning-of-line) (if (<= (point) lim) (goto-char (1+ lim))) (skip-chars-forward " \t")) (defun diesel-point (position) "Return the value of point at certain commonly referenced POSITIONs. POSITION can be one of the following symbols: `bol' -- beginning of line `eol' -- end of line `bod' -- beginning of defun `boi' -- back to indentation This function does not modify point or mark." (let ((here (point)) bufpos) (cond ((eq position 'bol) (beginning-of-line)) ((eq position 'eol) (end-of-line)) ((eq position 'bod) (diesel-beginning-of-defun)) ((eq position 'boi) (back-to-indentation)) ) (setq bufpos (point)) (goto-char here) bufpos)) ;; ====================================================================== ;; defuns for commenting out multiple lines. ;; ====================================================================== (defun diesel-comment-region (beg end) "Comment out all lines in a region between mark and current point by inserting `comment-start' in front of each line." (interactive "*r") (save-excursion (save-restriction (narrow-to-region (progn (goto-char beg) (beginning-of-line) (point)) (progn (goto-char end) (or (bolp) (forward-line 1)) (point))) (goto-char (point-min)) (while (not (eobp)) (insert comment-start) (forward-line 1))))) (defun diesel-uncomment-region (beg end) "Uncomment all lines in region between mark and current point by deleting the leading `comment-start' from each line, if any." (interactive "*r") (save-excursion (save-restriction (narrow-to-region (progn (goto-char beg) (beginning-of-line) (point)) (progn (goto-char end) (forward-line 1) (point))) (goto-char (point-min)) (let ((comment-regexp (concat "\\s *" (regexp-quote comment-start)))) (while (not (eobp)) (if (looking-at comment-regexp) (delete-region (match-beginning 0) (match-end 0))) (forward-line 1)))))) ;; Shamelessly lifted from cc-mode.el; need to do a better job. (defun diesel-fill-paragraph (&optional arg) "Like \\[fill-paragraph] but handles C and C++ style comments. If any of the current line is a comment or within a comment, fill the comment or the paragraph of it that point is in, preserving the comment indentation or line-starting decorations. Optional prefix ARG means justify paragraph as well." (interactive "P") (let* ((first-line ;; Check for obvious entry to comment. (save-excursion (beginning-of-line) (skip-chars-forward " \t\n") (looking-at comment-start-skip))) ;; This isn't quite right; cope with (-- --) (re1 "\\|[ \t]*--\\(DOC\\(SHORT\\|TEX\\|SKIP\\|ENDSKIP\\|BREAK\\)?\\)?[ \t]*$\\|[ \t-]*$") ) (if (save-excursion (beginning-of-line) (looking-at ".*--")) (let (fill-prefix ;; Lines containing just a comment start or just an end ;; should not be filled into paragraphs they are next ;; to. (paragraph-start (concat paragraph-start re1)) (paragraph-separate (concat paragraph-separate re1))) (save-excursion (beginning-of-line) ;; Move up to first line of this comment. (while (and (not (bobp)) (looking-at "[ \t]*--\\(DOC\\(SHORT\\|TEX\\|SKIP\\|ENDSKIP\\|BREAK\\)?\\)?[ \t]*[^ \t\n]")) (forward-line -1)) (if (not (looking-at ".*--\\(DOC\\(SHORT\\|TEX\\|SKIP\\|ENDSKIP\\|BREAK\\)?\\)?[ \t]*[^ \t\n]")) (forward-line 1)) ;; Find the comment start in this line. (re-search-forward "[ \t]*--\\(DOC\\(SHORT\\|TEX\\|SKIP\\|ENDSKIP\\|BREAK\\)?\\)?[ \t]*") ;; Set the fill-prefix to be what all lines except the first ;; should start with. (setq fill-prefix (buffer-substring (match-beginning 0) (match-end 0))) (save-restriction ;; Narrow down to just the lines of this comment. (narrow-to-region (diesel-point 'bol) (save-excursion (forward-line 1) (while (looking-at fill-prefix) (forward-line 1)) (point))) (fill-paragraph arg) t)))))) ;; ====================================================================== ;; grammar parsing ;; ====================================================================== (if (memq 'interactive-underscore diesel-emacs-features) (defun diesel-beginning-of-defun (&optional arg) "Find the beginning of the Diesel method, field, or object declaration." (interactive "_p") (diesel-beginning-of-defun-internal arg)) (defun diesel-beginning-of-defun (&optional arg) "Find the beginning of the Diesel method, field, or object declaration." (interactive "p") (diesel-beginning-of-defun-internal arg))) (defun diesel-beginning-of-defun-internal (&optional arg) "Find the beginning of the Diesel method, field, or object declaration." (if (not arg) (setq arg 1)) (cond ((or (= arg 0) (and (> arg 0) (bobp))) nil) ;; If between beginning of definition and some open brace (which is true in ;; particular if in header), move to beginning of definition. ((and (not (and (looking-at diesel-two-line-defun-header) ;; can skip a privacy declaration => not at begining of defun (not (diesel-skip-privacy-backward)))) (let ((curr-pos (point)) (open-pos (if (search-forward "{" nil 'move) (point))) (beg-pos (if (re-search-backward diesel-defun-header nil 'move) (progn (diesel-skip-privacy-backward) (point))))) (if (and open-pos beg-pos (< beg-pos curr-pos) (> open-pos curr-pos)) (progn (goto-char beg-pos) ;; If arg is negative (moving forward) we haven't made any ;; progress. Perhaps in that case, omit this entire ;; clause of the cond. (A potential problem occurs if ;; inside a privacy, so we'd move only past that.) (if (> arg 0) (setq arg (- arg 1))) (= arg 0)) ;; Are we done? (goto-char curr-pos) nil)))) (t (if (and (looking-at diesel-two-line-defun-header) (not (bobp))) (if (< arg 0) (progn (diesel-skip-privacy-forward) (forward-char 1)) (diesel-skip-privacy-backward) (forward-char -1))) (and (re-search-backward diesel-defun-header nil 'move (or arg 1)) (progn (goto-char (match-beginning 0)) (diesel-skip-privacy-backward)))))) (defun diesel-skip-privacy-backward () "Skip backwards over any Diesel privacy declaration on preceding line. Return nil if didn't skip one, non-nil if did skip one." (if (save-excursion (forward-line -1) (looking-at (concat "[ \t]*" diesel-privacy-nonoptional-re "[ \t]*$"))) (forward-line -1))) (defun diesel-skip-privacy-forward () "Skip backwards over any Diesel privacy declaration on current line. Return nil if didn't skip one, non-nil if did skip one." (if (looking-at (concat "[ \t]*" diesel-privacy-nonoptional-re "[ \t]*$")) ;; equivalently, (forward-line 1) (goto-char (1+ (match-end 0))))) (if (memq 'interactive-underscore diesel-emacs-features) (defun diesel-end-of-defun (arg) "Find the end of the Diesel function." (interactive "_p") (diesel-end-of-defun-internal arg)) (defun diesel-end-of-defun (arg) "Find the end of the Diesel function." (interactive "p") (diesel-end-of-defun-internal arg))) (defun diesel-end-of-defun-internal (arg) "Find the end of the Diesel function." ;; If between definitions, get in the next one (diesel-skip-whitespace) (diesel-beginning-of-defun-internal (- arg)) (diesel-backward-syntactic-ws) ;; put point at beginning of first line after the definition (forward-line 1)) ;; Old definition. I don't understand what's going on, and parts are clearly ;; wrong (e.g., arg for (- arg) in line 11). -MDE 11/12/96 ;; (defun diesel-end-of-defun-internal (arg) ;; "Find the end of the Diesel function." ;; (let ((parse-sexp-ignore-comments t)) ;; (if (and (eobp) (> arg 0)) ;; nil ;; (if (and (> arg 0) (looking-at diesel-defun-header)) (forward-char 1)) ;; (let ((pos (point))) ;; (diesel-beginning-of-defun ;; (if (< arg 0) ;; (- (- arg (if (eobp) 0 1))) ;; arg)) ;; (if (and (< arg 0) (bobp)) ;; t ;; (if (re-search-forward diesel-defun-header nil 'move) ;; (progn (forward-char -1) ;; (forward-sexp) ;; (beginning-of-line 2))) ;; (if (and (= pos (point)) ;; (re-search-forward diesel-defun-header nil 'move)) ;; (diesel-end-of-defun 1)))) ;; t))) (defun diesel-indent-defun () "Indent the current declaration." (interactive) (let ((restore (point))) (diesel-end-of-defun 1) (beginning-of-line 1) (let ((end (point-marker))) (diesel-beginning-of-defun) (while (and (< (point) end)) (diesel-indent-line) (forward-line 1) (beginning-of-line 1)) (set-marker end nil)) (goto-char restore))) ;; ====================================================================== ;; defuns for submitting bug reports ;; ====================================================================== (defconst diesel-version "1.0" "diesel-mode version number.") (defconst diesel-mode-help-address "diesel@cs.washington.edu" "Address accepting submission of bug reports.") (defun diesel-version () "Echo the current version of diesel-mode." (interactive) (message "Using diesel-mode.el %s" diesel-version)) ;; indicate diesel-mode has been loaded (provide 'diesel-mode) ;;; diesel-mode.el ends here