diff options
| author | Karl Heuer | 1999-07-26 19:01:27 +0000 |
|---|---|---|
| committer | Karl Heuer | 1999-07-26 19:01:27 +0000 |
| commit | 70492703cb1c9993b223393e189c8cb152db1c10 (patch) | |
| tree | 86e80e5608653daaa830d0c52dd9ce264565f617 /lisp | |
| parent | 2b8503eae9210941144e0eefe73cbeba3dd531b3 (diff) | |
| download | emacs-70492703cb1c9993b223393e189c8cb152db1c10.tar.gz emacs-70492703cb1c9993b223393e189c8cb152db1c10.zip | |
Initial revision
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/progmodes/delphi.el | 1975 |
1 files changed, 1975 insertions, 0 deletions
diff --git a/lisp/progmodes/delphi.el b/lisp/progmodes/delphi.el new file mode 100644 index 00000000000..5409e2970c7 --- /dev/null +++ b/lisp/progmodes/delphi.el | |||
| @@ -0,0 +1,1975 @@ | |||
| 1 | ;; delphi.el --- Major mode for editing Delphi source (Object Pascal) in Emacs | ||
| 2 | |||
| 3 | ;; Copyright (C) 1998, 1999 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Ray Blaak <blaak@infomatch.com> | ||
| 6 | ;; Keywords: languages | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 13 | ;; any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 22 | ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 23 | ;; Boston, MA 02111-1307, USA. | ||
| 24 | |||
| 25 | ;;; Commentary: | ||
| 26 | |||
| 27 | ;; To enter Delphi mode when you find a Delphi source file, one must override | ||
| 28 | ;; the auto-mode-alist to associate Delphi with .pas (and .dpr and .dpk) | ||
| 29 | ;; files. Emacs, by default, will otherwise enter Pascal mode. E.g. | ||
| 30 | ;; | ||
| 31 | ;; (autoload 'delphi-mode "delphi") | ||
| 32 | ;; (setq auto-mode-alist | ||
| 33 | ;; (cons '("\\.\\(pas\\|dpr\\|dpk\\)$" . delphi-mode) auto-mode-alist)) | ||
| 34 | |||
| 35 | ;; When you have entered Delphi mode, you may get more info by pressing | ||
| 36 | ;; C-h m. | ||
| 37 | |||
| 38 | ;; This delphi mode implementation is fairly tolerant of syntax | ||
| 39 | ;; errors, relying as much as possible on the indentation of the | ||
| 40 | ;; previous statement. This also makes it faster and simpler, since | ||
| 41 | ;; there is less searching for properly constructed beginnings. | ||
| 42 | |||
| 43 | ;;; Code: | ||
| 44 | |||
| 45 | (provide 'delphi) | ||
| 46 | |||
| 47 | (defconst delphi-version | ||
| 48 | (let ((revision "$Revision: 2.5 $")) | ||
| 49 | (string-match ": \\([^ ]+\\)" revision) | ||
| 50 | (match-string 1 revision)) | ||
| 51 | "Version of this delphi mode.") | ||
| 52 | ;;; $Log: delphi.el,v $ | ||
| 53 | ;;; Revision 2.5 1999/07/24 21:45:22 blaak | ||
| 54 | ;;; Misc changes to make debugging easier, and to conform to major mode | ||
| 55 | ;;; standards (i.e. don't require font-lock -- that forces its existence). | ||
| 56 | ;;; | ||
| 57 | ;;; Revision 2.3 1999/07/20 07:55:38 blaak | ||
| 58 | ;;; o One can now undo the effects of delphi-fill-comment. | ||
| 59 | ;;; o Object declarations are now formatted properly. | ||
| 60 | ;;; | ||
| 61 | ;;; Revision 2.2 1999/07/01 04:16:07 blaak | ||
| 62 | ;;; Handle multiple parameters on a line correctly, e.g., | ||
| 63 | ;;; procedure Foo (arg1 : T; arg2 : T; arg3 : T; | ||
| 64 | ;;; arg3 : T; // should be aligned with arg1, | ||
| 65 | ;;; // not arg2 or arg3 | ||
| 66 | ;;; | ||
| 67 | ;;; Revision 2.1 1999/05/21 06:37:19 blaak | ||
| 68 | ;;; Indent properly after simple class declarations, even if they span | ||
| 69 | ;;; multiple lines. E.g. | ||
| 70 | ;;; type TMetaClass = class of TClass; | ||
| 71 | ;;; type TSimpleClass = class | ||
| 72 | ;;; (TBaseClass); | ||
| 73 | ;;; type TForwardClass = class; | ||
| 74 | ;;; | ||
| 75 | ;;; Revision 2.0 1999/05/09 06:31:53 blaak | ||
| 76 | ;;; Re-release. Pre 2.0 versions considered development versions and their | ||
| 77 | ;;; histories dropped. Delphi mode is what it is. | ||
| 78 | ;;; | ||
| 79 | ;;; | ||
| 80 | |||
| 81 | (eval-and-compile | ||
| 82 | ;; Allow execution on pre Emacs 20 versions. | ||
| 83 | (or (fboundp 'when) | ||
| 84 | (defmacro when (test &rest body) | ||
| 85 | `(if ,test (progn ,@body)))) | ||
| 86 | (or (fboundp 'unless) | ||
| 87 | (defmacro unless (test &rest body) | ||
| 88 | `(if (not ,test) (progn ,@body)))) | ||
| 89 | (or (fboundp 'defgroup) | ||
| 90 | (defmacro defgroup (group val docs &rest group-attributes) | ||
| 91 | `(defvar ,group ,val ,docs))) | ||
| 92 | (or (fboundp 'defcustom) | ||
| 93 | (defmacro defcustom (val-name val docs &rest custom-attributes) | ||
| 94 | `(defvar ,val-name ,val ,docs))) | ||
| 95 | (or (fboundp 'cadr) | ||
| 96 | (defmacro cadr (list) `(car (cdr ,list)))) | ||
| 97 | (or (fboundp 'cddr) | ||
| 98 | (defmacro cddr (list) `(cdr (cdr ,list)))) | ||
| 99 | (or (fboundp 'with-current-buffer) | ||
| 100 | (defmacro with-current-buffer (buf &rest forms) | ||
| 101 | `(save-excursion (set-buffer ,buf) ,@forms))) | ||
| 102 | ) | ||
| 103 | |||
| 104 | (defgroup delphi nil | ||
| 105 | "Major mode for editing Delphi source in Emacs" | ||
| 106 | :group 'languages) | ||
| 107 | |||
| 108 | (defconst delphi-debug nil | ||
| 109 | "True if in debug mode.") | ||
| 110 | |||
| 111 | (defcustom delphi-search-path "." | ||
| 112 | "*Directories to search when finding external units. It is a list of | ||
| 113 | directory strings. If only a single directory, it can be a single | ||
| 114 | string instead of a list. If a directory ends in \"...\" then that | ||
| 115 | directory is recursively searched." | ||
| 116 | :type 'string | ||
| 117 | :group 'delphi) | ||
| 118 | |||
| 119 | (defcustom delphi-indent-level 3 | ||
| 120 | "*Indentation of Delphi statements with respect to containing block. E.g. | ||
| 121 | |||
| 122 | begin | ||
| 123 | // This is an indent of 3. | ||
| 124 | end;" | ||
| 125 | :type 'integer | ||
| 126 | :group 'delphi) | ||
| 127 | |||
| 128 | (defcustom delphi-compound-block-indent 0 | ||
| 129 | "*Extra indentation for blocks in compound statements. E.g. | ||
| 130 | |||
| 131 | // block indent = 0 vs // block indent = 2 | ||
| 132 | if b then if b then | ||
| 133 | begin begin | ||
| 134 | end else begin end | ||
| 135 | end; else | ||
| 136 | begin | ||
| 137 | end;" | ||
| 138 | :type 'integer | ||
| 139 | :group 'delphi) | ||
| 140 | |||
| 141 | (defcustom delphi-case-label-indent delphi-indent-level | ||
| 142 | "*Extra indentation for case statement labels. E.g. | ||
| 143 | |||
| 144 | // case indent = 0 vs // case indent = 3 | ||
| 145 | case value of case value of | ||
| 146 | v1: process_v1; v1: process_v1; | ||
| 147 | v2: process_v2; v2: process_v2; | ||
| 148 | else else | ||
| 149 | process_else; process_else; | ||
| 150 | end; end;" | ||
| 151 | :type 'integer | ||
| 152 | :group 'delphi) | ||
| 153 | |||
| 154 | (defcustom delphi-verbose t ; nil | ||
| 155 | "*If true then delphi token processing progress is reported to the user." | ||
| 156 | :type 'boolean | ||
| 157 | :group 'delphi) | ||
| 158 | |||
| 159 | (defcustom delphi-tab-always-indent t | ||
| 160 | "*Non-nil means TAB in Delphi mode should always reindent the current line, | ||
| 161 | regardless of where in the line point is when the TAB command is used." | ||
| 162 | :type 'boolean | ||
| 163 | :group 'delphi) | ||
| 164 | |||
| 165 | (defcustom delphi-comment-face 'font-lock-comment-face | ||
| 166 | "*Face used to color delphi comments." | ||
| 167 | :type 'facep | ||
| 168 | :group 'delphi) | ||
| 169 | |||
| 170 | (defcustom delphi-string-face 'font-lock-string-face | ||
| 171 | "*Face used to color delphi strings." | ||
| 172 | :type 'facep | ||
| 173 | :group 'delphi) | ||
| 174 | |||
| 175 | (defcustom delphi-keyword-face 'font-lock-keyword-face | ||
| 176 | "*Face used to color delphi keywords." | ||
| 177 | :type 'facep | ||
| 178 | :group 'delphi) | ||
| 179 | |||
| 180 | (defcustom delphi-other-face nil | ||
| 181 | "*Face used to color everything else." | ||
| 182 | :type 'facep | ||
| 183 | :group 'delphi) | ||
| 184 | |||
| 185 | (defconst delphi-directives | ||
| 186 | '(absolute abstract assembler automated cdecl default dispid dynamic | ||
| 187 | export external far forward index inline message name near nodefault | ||
| 188 | overload override pascal private protected public published read readonly | ||
| 189 | register reintroduce resident resourcestring safecall stdcall stored | ||
| 190 | virtual write writeonly) | ||
| 191 | "Delphi4 directives.") | ||
| 192 | |||
| 193 | (defconst delphi-keywords | ||
| 194 | (append | ||
| 195 | '(;; Keywords. | ||
| 196 | and array as asm at begin case class const constructor contains | ||
| 197 | destructor dispinterface div do downto else end except exports | ||
| 198 | file finalization finally for function goto if implementation implements | ||
| 199 | in inherited initialization interface is label library mod nil not | ||
| 200 | of object on or out package packed procedure program property | ||
| 201 | raise record repeat requires result self set shl shr then threadvar | ||
| 202 | to try type unit uses until var while with xor | ||
| 203 | |||
| 204 | ;; These routines should be keywords, if Borland had the balls. | ||
| 205 | break exit) | ||
| 206 | |||
| 207 | ;; We want directives to look like keywords. | ||
| 208 | delphi-directives) | ||
| 209 | "Delphi4 keywords.") | ||
| 210 | |||
| 211 | (defconst delphi-previous-terminators `(semicolon comma) | ||
| 212 | "Expression/statement terminators that denote a previous expression.") | ||
| 213 | |||
| 214 | (defconst delphi-comments | ||
| 215 | '(comment-single-line comment-multi-line-1 comment-multi-line-2) | ||
| 216 | "Tokens that represent comments.") | ||
| 217 | |||
| 218 | (defconst delphi-strings | ||
| 219 | '(string double-quoted-string) | ||
| 220 | "Tokens that represent string literals.") | ||
| 221 | |||
| 222 | (defconst delphi-whitespace `(space newline ,@delphi-comments) | ||
| 223 | "Tokens that are considered whitespace.") | ||
| 224 | |||
| 225 | (defconst delphi-routine-statements | ||
| 226 | '(procedure function constructor destructor property) | ||
| 227 | "Marks the start of a routine, or routine-ish looking expression.") | ||
| 228 | |||
| 229 | (defconst delphi-body-expr-statements '(if while for on) | ||
| 230 | "Statements that have either a single statement or a block as a body and also | ||
| 231 | are followed by an expression.") | ||
| 232 | |||
| 233 | (defconst delphi-expr-statements `(case ,@delphi-body-expr-statements) | ||
| 234 | "Expression statements contain expressions after their keyword.") | ||
| 235 | |||
| 236 | (defconst delphi-body-statements `(else ,@delphi-body-expr-statements) | ||
| 237 | "Statements that have either a single statement or a block as a body.") | ||
| 238 | |||
| 239 | (defconst delphi-expr-delimiters '(then do of) | ||
| 240 | "Expression delimiter tokens.") | ||
| 241 | |||
| 242 | (defconst delphi-binary-ops | ||
| 243 | '(plus minus equals not-equals times divides div mod and or xor) | ||
| 244 | "Delphi binary operations.") | ||
| 245 | |||
| 246 | (defconst delphi-visibilities '(public private protected published automated) | ||
| 247 | "Class visibilities.") | ||
| 248 | |||
| 249 | (defconst delphi-block-statements | ||
| 250 | '(begin try case repeat initialization finalization) | ||
| 251 | "Statements that contain multiple substatements.") | ||
| 252 | |||
| 253 | (defconst delphi-mid-block-statements | ||
| 254 | `(except finally ,@delphi-visibilities) | ||
| 255 | "Statements that mark mid sections of the enclosing block.") | ||
| 256 | |||
| 257 | (defconst delphi-end-block-statements `(end until) | ||
| 258 | "Statements that end block sections.") | ||
| 259 | |||
| 260 | (defconst delphi-match-block-statements | ||
| 261 | `(,@delphi-end-block-statements ,@delphi-mid-block-statements) | ||
| 262 | "Statements that match the indentation of the parent block.") | ||
| 263 | |||
| 264 | (defconst delphi-decl-sections '(type const var label) | ||
| 265 | "Denotes the start of a declaration section.") | ||
| 266 | |||
| 267 | (defconst delphi-class-types '(class object) | ||
| 268 | "Class types.") | ||
| 269 | |||
| 270 | (defconst delphi-composite-types `(,@delphi-class-types record) | ||
| 271 | "Types that contain declarations within them.") | ||
| 272 | |||
| 273 | (defconst delphi-unit-sections | ||
| 274 | '(interface implementation program library package) | ||
| 275 | "Unit sections within which the indent is 0.") | ||
| 276 | |||
| 277 | (defconst delphi-use-clauses `(uses requires exports contains) | ||
| 278 | "Statements that refer to foreign symbols.") | ||
| 279 | |||
| 280 | (defconst delphi-unit-statements | ||
| 281 | `(,@delphi-use-clauses ,@delphi-unit-sections initialization finalization) | ||
| 282 | "Statements indented at level 0.") | ||
| 283 | |||
| 284 | (defconst delphi-decl-delimiters | ||
| 285 | `(,@delphi-decl-sections ,@delphi-unit-statements | ||
| 286 | ,@delphi-routine-statements) | ||
| 287 | "Statements that a declaration statement should align with.") | ||
| 288 | |||
| 289 | (defconst delphi-decl-matchers | ||
| 290 | `(begin ,@delphi-decl-sections) | ||
| 291 | "Statements that should match to declaration statement indentation.") | ||
| 292 | |||
| 293 | (defconst delphi-enclosing-statements | ||
| 294 | `(,@delphi-block-statements ,@delphi-mid-block-statements | ||
| 295 | ,@delphi-decl-sections ,@delphi-use-clauses ,@delphi-routine-statements) | ||
| 296 | "Delimits an enclosing statement.") | ||
| 297 | |||
| 298 | (defconst delphi-previous-statements | ||
| 299 | `(,@delphi-unit-statements ,@delphi-routine-statements) | ||
| 300 | "Delimits a previous statement.") | ||
| 301 | |||
| 302 | (defconst delphi-previous-enclosing-statements | ||
| 303 | `(,@delphi-block-statements ,@delphi-mid-block-statements | ||
| 304 | ,@delphi-decl-sections) | ||
| 305 | "Delimits a previous enclosing statement.") | ||
| 306 | |||
| 307 | (defconst delphi-begin-enclosing-tokens | ||
| 308 | `(,@delphi-block-statements ,@delphi-mid-block-statements) | ||
| 309 | "Tokens that a begin token indents from.") | ||
| 310 | |||
| 311 | (defconst delphi-begin-previous-tokens | ||
| 312 | `(,@delphi-decl-sections ,@delphi-routine-statements) | ||
| 313 | "Tokens that a begin token aligns with, but only if not part of a nested | ||
| 314 | routine.") | ||
| 315 | |||
| 316 | (defconst delphi-space-chars "\000-\011\013- ") ; all except \n | ||
| 317 | (defconst delphi-non-space-chars (concat "^" delphi-space-chars)) | ||
| 318 | (defconst delphi-spaces-re (concat "[" delphi-space-chars "]*")) | ||
| 319 | (defconst delphi-leading-spaces-re (concat "^" delphi-spaces-re)) | ||
| 320 | (defconst delphi-word-chars "a-zA-Z0-9_") | ||
| 321 | |||
| 322 | (defmacro delphi-save-match-data (&rest forms) | ||
| 323 | ;; Executes the forms such that the current match data is preserved, so as | ||
| 324 | ;; not to disturb any existing search results. | ||
| 325 | `(let ((data (match-data))) | ||
| 326 | (unwind-protect | ||
| 327 | (progn ,@forms) | ||
| 328 | (set-match-data data)))) | ||
| 329 | |||
| 330 | (defmacro delphi-save-excursion (&rest forms) | ||
| 331 | ;; Executes the forms such that any movements have no effect, including | ||
| 332 | ;; searches. | ||
| 333 | `(save-excursion | ||
| 334 | (delphi-save-match-data | ||
| 335 | (let ((inhibit-point-motion-hooks t) | ||
| 336 | (deactivate-mark nil)) | ||
| 337 | (progn ,@forms))))) | ||
| 338 | |||
| 339 | (defmacro delphi-save-state (&rest forms) | ||
| 340 | ;; Executes the forms such that any buffer modifications do not have any side | ||
| 341 | ;; effects beyond the buffer's actual content changes. | ||
| 342 | `(let ((delphi-ignore-changes t) | ||
| 343 | (old-supersession-threat | ||
| 344 | (symbol-function 'ask-user-about-supersession-threat)) | ||
| 345 | (buffer-read-only nil) | ||
| 346 | (inhibit-read-only t) | ||
| 347 | (buffer-undo-list t) | ||
| 348 | (before-change-functions nil) | ||
| 349 | (after-change-functions nil) | ||
| 350 | (modified (buffer-modified-p))) | ||
| 351 | ;; Disable any queries about editing obsolete files. | ||
| 352 | (fset 'ask-user-about-supersession-threat (lambda (fn))) | ||
| 353 | (unwind-protect | ||
| 354 | (progn ,@forms) | ||
| 355 | (set-buffer-modified-p modified) | ||
| 356 | (fset 'ask-user-about-supersession-threat old-supersession-threat)))) | ||
| 357 | |||
| 358 | (defsubst delphi-is (element in-set) | ||
| 359 | ;; If the element is in the set, the element cdr is returned, otherwise nil. | ||
| 360 | (memq element in-set)) | ||
| 361 | |||
| 362 | (defun delphi-string-of (start end) | ||
| 363 | ;; Returns the buffer string from start to end. | ||
| 364 | (buffer-substring-no-properties start end)) | ||
| 365 | |||
| 366 | (defun delphi-looking-at-string (p s) | ||
| 367 | ;; True if point p marks the start of string s. s is not a regular | ||
| 368 | ;; expression. | ||
| 369 | (let ((limit (+ p (length s)))) | ||
| 370 | (and (<= limit (point-max)) | ||
| 371 | (string= s (delphi-string-of p limit))))) | ||
| 372 | |||
| 373 | (defun delphi-token-of (kind start end) | ||
| 374 | ;; Constructs a token from a kind symbol and its start/end points. | ||
| 375 | `[,kind ,start ,end]) | ||
| 376 | |||
| 377 | (defsubst delphi-token-kind (token) | ||
| 378 | ;; Returns the kind symbol of the token. | ||
| 379 | (if token (aref token 0) nil)) | ||
| 380 | |||
| 381 | (defun delphi-set-token-kind (token to-kind) | ||
| 382 | ;; Sets the kind symbol of the token. | ||
| 383 | (if token (aset token 0 to-kind))) | ||
| 384 | |||
| 385 | (defsubst delphi-token-start (token) | ||
| 386 | ;; Returns the start point of the token. | ||
| 387 | (if token (aref token 1) (point-min))) | ||
| 388 | |||
| 389 | (defsubst delphi-token-end (token) | ||
| 390 | ;; Returns the end point of the token. | ||
| 391 | (if token (aref token 2) (point-min))) | ||
| 392 | |||
| 393 | (defun delphi-set-token-start (token start) | ||
| 394 | ;; Sets the start point of the token. | ||
| 395 | (if token (aset token 1 start))) | ||
| 396 | |||
| 397 | (defun delphi-set-token-end (token end) | ||
| 398 | ;; Sets the end point of the token. | ||
| 399 | (if token (aset token 2 end))) | ||
| 400 | |||
| 401 | (defun delphi-token-string (token) | ||
| 402 | ;; Returns the string image of the token. | ||
| 403 | (if token | ||
| 404 | (delphi-string-of (delphi-token-start token) (delphi-token-end token)) | ||
| 405 | "")) | ||
| 406 | |||
| 407 | (defun delphi-in-token (p token) | ||
| 408 | ;; Returns true if the point p is within the token's start/end points. | ||
| 409 | (and (<= (delphi-token-start token) p) (< p (delphi-token-end token)))) | ||
| 410 | |||
| 411 | (defun delphi-column-of (p) | ||
| 412 | ;; Returns the column of the point p. | ||
| 413 | (save-excursion (goto-char p) (current-column))) | ||
| 414 | |||
| 415 | (defun delphi-face-of (token-kind) | ||
| 416 | ;; Returns the face property appropriate for the token kind. | ||
| 417 | (cond ((delphi-is token-kind delphi-comments) delphi-comment-face) | ||
| 418 | ((delphi-is token-kind delphi-strings) delphi-string-face) | ||
| 419 | ((delphi-is token-kind delphi-keywords) delphi-keyword-face) | ||
| 420 | (delphi-other-face))) | ||
| 421 | |||
| 422 | (defvar delphi-progress-last-reported-point nil | ||
| 423 | "The last point at which progress was reported.") | ||
| 424 | |||
| 425 | (defconst delphi-parsing-progress-step 16384 | ||
| 426 | "Number of chars to process before the next parsing progress report.") | ||
| 427 | (defconst delphi-scanning-progress-step 2048 | ||
| 428 | "Number of chars to process before the next scanning progress report.") | ||
| 429 | (defconst delphi-fontifying-progress-step delphi-scanning-progress-step | ||
| 430 | "Number of chars to process before the next fontification progress report.") | ||
| 431 | |||
| 432 | (defun delphi-progress-start () | ||
| 433 | ;; Initializes progress reporting. | ||
| 434 | (setq delphi-progress-last-reported-point nil)) | ||
| 435 | |||
| 436 | (defun delphi-progress-done (&rest msgs) | ||
| 437 | ;; Finalizes progress reporting. | ||
| 438 | (setq delphi-progress-last-reported-point nil) | ||
| 439 | (when delphi-verbose | ||
| 440 | (if (null msgs) | ||
| 441 | (message "") | ||
| 442 | (apply #'message msgs)))) | ||
| 443 | |||
| 444 | (defun delphi-step-progress (p desc step-size) | ||
| 445 | ;; If enough distance has elapsed since the last reported point, then report | ||
| 446 | ;; the current progress to the user. | ||
| 447 | (cond ((null delphi-progress-last-reported-point) | ||
| 448 | ;; This is the first progress step. | ||
| 449 | (setq delphi-progress-last-reported-point p)) | ||
| 450 | |||
| 451 | ((and delphi-verbose | ||
| 452 | (>= (abs (- p delphi-progress-last-reported-point)) step-size)) | ||
| 453 | ;; Report the percentage complete. | ||
| 454 | (setq delphi-progress-last-reported-point p) | ||
| 455 | (message "%s %s ... %d%%" | ||
| 456 | desc (buffer-name) (/ (* 100 p) (point-max)))))) | ||
| 457 | |||
| 458 | (defun delphi-next-line-start (&optional from-point) | ||
| 459 | ;; Returns the first point of the next line. | ||
| 460 | (let ((curr-point (point)) | ||
| 461 | (next nil)) | ||
| 462 | (if from-point (goto-char from-point)) | ||
| 463 | (end-of-line) | ||
| 464 | (setq next (min (1+ (point)) (point-max))) | ||
| 465 | (goto-char curr-point) | ||
| 466 | next)) | ||
| 467 | |||
| 468 | (defun delphi-set-text-properties (from to properties) | ||
| 469 | ;; Like `set-text-properties', except we do not consider this to be a buffer | ||
| 470 | ;; modification. | ||
| 471 | (delphi-save-state | ||
| 472 | (set-text-properties from to properties))) | ||
| 473 | |||
| 474 | (defun delphi-literal-kind (p) | ||
| 475 | ;; Returns the literal kind the point p is in (or nil if not in a literal). | ||
| 476 | (if (and (<= (point-min) p) (<= p (point-max))) | ||
| 477 | (get-text-property p 'token))) | ||
| 478 | |||
| 479 | (defun delphi-literal-start-pattern (literal-kind) | ||
| 480 | ;; Returns the start pattern of the literal kind. | ||
| 481 | (cdr (assoc literal-kind | ||
| 482 | '((comment-single-line . "//") | ||
| 483 | (comment-multi-line-1 . "{") | ||
| 484 | (comment-multi-line-2 . "(*") | ||
| 485 | (string . "'") | ||
| 486 | (double-quoted-string . "\""))))) | ||
| 487 | |||
| 488 | (defun delphi-literal-end-pattern (literal-kind) | ||
| 489 | ;; Returns the end pattern of the literal kind. | ||
| 490 | (cdr (assoc literal-kind | ||
| 491 | '((comment-single-line . "\n") | ||
| 492 | (comment-multi-line-1 . "}") | ||
| 493 | (comment-multi-line-2 . "*)") | ||
| 494 | (string . "'") | ||
| 495 | (double-quoted-string . "\""))))) | ||
| 496 | |||
| 497 | (defun delphi-literal-stop-pattern (literal-kind) | ||
| 498 | ;; Returns the pattern that delimits end of the search for the literal kind. | ||
| 499 | ;; These are regular expressions. | ||
| 500 | (cdr (assoc literal-kind | ||
| 501 | '((comment-single-line . "\n") | ||
| 502 | (comment-multi-line-1 . "}") | ||
| 503 | (comment-multi-line-2 . "\\*)") | ||
| 504 | ;; Strings cannot span lines. | ||
| 505 | (string . "['\n]") | ||
| 506 | (double-quoted-string . "[\"\n]"))))) | ||
| 507 | |||
| 508 | (defun delphi-is-literal-start (p) | ||
| 509 | ;; True if the point p is at the end point of a (completed) literal. | ||
| 510 | (let* ((kind (delphi-literal-kind p)) | ||
| 511 | (pattern (delphi-literal-start-pattern kind))) | ||
| 512 | (or (null kind) ; Non-literals are considered as start points. | ||
| 513 | (delphi-looking-at-string p pattern)))) | ||
| 514 | |||
| 515 | (defun delphi-is-literal-end (p) | ||
| 516 | ;; True if the point p is at the end point of a (completed) literal. | ||
| 517 | (let* ((kind (delphi-literal-kind (1- p))) | ||
| 518 | (pattern (delphi-literal-end-pattern kind))) | ||
| 519 | (or (null kind) ; Non-literals are considered as end points. | ||
| 520 | |||
| 521 | (and (delphi-looking-at-string (- p (length pattern)) pattern) | ||
| 522 | (or (not (delphi-is kind delphi-strings)) | ||
| 523 | ;; Special case: string delimiters are start/end ambiguous. | ||
| 524 | ;; We have an end only if there is some string content (at | ||
| 525 | ;; least a starting delimiter). | ||
| 526 | (not (delphi-is-literal-end (1- p))))) | ||
| 527 | |||
| 528 | ;; Special case: strings cannot span lines. | ||
| 529 | (and (delphi-is kind delphi-strings) (eq ?\n (char-after (1- p))))))) | ||
| 530 | |||
| 531 | (defun delphi-is-stable-literal (p) | ||
| 532 | ;; True if the point p marks a stable point. That is, a point outside of a | ||
| 533 | ;; literal region, inside of a literal region, or adjacent to completed | ||
| 534 | ;; literal regions. | ||
| 535 | (let ((at-start (delphi-is-literal-start p)) | ||
| 536 | (at-end (delphi-is-literal-end p))) | ||
| 537 | (or (>= p (point-max)) | ||
| 538 | (and at-start at-end) | ||
| 539 | (and (not at-start) (not at-end) | ||
| 540 | (eq (delphi-literal-kind (1- p)) (delphi-literal-kind p)))))) | ||
| 541 | |||
| 542 | (defun delphi-complete-literal (literal-kind limit) | ||
| 543 | ;; Continues the search for a literal's true end point and returns the | ||
| 544 | ;; point past the end pattern (if found) or the limit (if not found). | ||
| 545 | (let ((pattern (delphi-literal-stop-pattern literal-kind))) | ||
| 546 | (if (not (stringp pattern)) | ||
| 547 | (error "Invalid literal kind %S" literal-kind) | ||
| 548 | ;; Search up to the limit. | ||
| 549 | (re-search-forward pattern limit 'goto-limit-on-fail) | ||
| 550 | (point)))) | ||
| 551 | |||
| 552 | (defun delphi-literal-text-properties (kind) | ||
| 553 | ;; Creates a list of text properties for the literal kind. | ||
| 554 | (if (and (boundp 'font-lock-mode) | ||
| 555 | font-lock-mode) | ||
| 556 | (list 'token kind 'face (delphi-face-of kind) 'lazy-lock t) | ||
| 557 | (list 'token kind))) | ||
| 558 | |||
| 559 | (defun delphi-parse-next-literal (limit) | ||
| 560 | ;; Searches for the next literal region (i.e. comment or string) and sets the | ||
| 561 | ;; the point to its end (or the limit, if not found). The literal region is | ||
| 562 | ;; marked as such with a text property, to speed up tokenizing during face | ||
| 563 | ;; coloring and indentation scanning. | ||
| 564 | (let ((search-start (point))) | ||
| 565 | (cond ((not (delphi-is-literal-end search-start)) | ||
| 566 | ;; We are completing an incomplete literal. | ||
| 567 | (let ((kind (delphi-literal-kind (1- search-start)))) | ||
| 568 | (delphi-complete-literal kind limit) | ||
| 569 | (delphi-set-text-properties | ||
| 570 | search-start (point) (delphi-literal-text-properties kind)))) | ||
| 571 | |||
| 572 | ((re-search-forward | ||
| 573 | "\\(//\\)\\|\\({\\)\\|\\((\\*\\)\\|\\('\\)\\|\\(\"\\)" | ||
| 574 | limit 'goto-limit-on-fail) | ||
| 575 | ;; We found the start of a new literal. Find its end and mark it. | ||
| 576 | (let ((kind (cond ((match-beginning 1) 'comment-single-line) | ||
| 577 | ((match-beginning 2) 'comment-multi-line-1) | ||
| 578 | ((match-beginning 3) 'comment-multi-line-2) | ||
| 579 | ((match-beginning 4) 'string) | ||
| 580 | ((match-beginning 5) 'double-quoted-string))) | ||
| 581 | (start (match-beginning 0))) | ||
| 582 | (delphi-set-text-properties search-start start nil) | ||
| 583 | (delphi-complete-literal kind limit) | ||
| 584 | (delphi-set-text-properties | ||
| 585 | start (point) (delphi-literal-text-properties kind)))) | ||
| 586 | |||
| 587 | ;; Nothing found. Mark it as a non-literal. | ||
| 588 | ((delphi-set-text-properties search-start limit nil))) | ||
| 589 | (delphi-step-progress (point) "Parsing" delphi-parsing-progress-step))) | ||
| 590 | |||
| 591 | (defun delphi-literal-token-at (p) | ||
| 592 | ;; Returns the literal token surrounding the point p, or nil if none. | ||
| 593 | (let ((kind (delphi-literal-kind p))) | ||
| 594 | (when kind | ||
| 595 | (let ((start (previous-single-property-change (1+ p) 'token)) | ||
| 596 | (end (next-single-property-change p 'token))) | ||
| 597 | (delphi-token-of kind (or start (point-min)) (or end (point-max))))))) | ||
| 598 | |||
| 599 | (defun delphi-point-token-at (p kind) | ||
| 600 | ;; Returns the single character token at the point p. | ||
| 601 | (delphi-token-of kind p (1+ p))) | ||
| 602 | |||
| 603 | (defsubst delphi-char-token-at (p char kind) | ||
| 604 | ;; Returns the token at the point p that describes the specified character. | ||
| 605 | ;; If not actually over such a character, nil is returned. | ||
| 606 | (when (eq char (char-after p)) | ||
| 607 | (delphi-token-of kind p (1+ p)))) | ||
| 608 | |||
| 609 | (defun delphi-charset-token-at (p charset kind) | ||
| 610 | ;; Returns the token surrounding point p that contains only members of the | ||
| 611 | ;; character set. | ||
| 612 | (let ((currp (point)) | ||
| 613 | (end nil) | ||
| 614 | (start nil) | ||
| 615 | (token nil)) | ||
| 616 | (goto-char p) | ||
| 617 | (when (> (skip-chars-forward charset) 0) | ||
| 618 | (setq end (point)) | ||
| 619 | (goto-char (1+ p)) | ||
| 620 | (skip-chars-backward charset) | ||
| 621 | (setq token (delphi-token-of kind (point) end))) | ||
| 622 | (goto-char currp) | ||
| 623 | token)) | ||
| 624 | |||
| 625 | (defun delphi-space-token-at (p) | ||
| 626 | ;; If point p is surrounded by space characters, then return the token of the | ||
| 627 | ;; contiguous spaces. | ||
| 628 | (delphi-charset-token-at p delphi-space-chars 'space)) | ||
| 629 | |||
| 630 | (defun delphi-word-token-at (p) | ||
| 631 | ;; If point p is over a word (i.e. identifier characters), then return a word | ||
| 632 | ;; token. If the word is actually a keyword, then return the keyword token. | ||
| 633 | (let ((word (delphi-charset-token-at p delphi-word-chars 'word))) | ||
| 634 | (when word | ||
| 635 | (let* ((word-image (downcase (delphi-token-string word))) | ||
| 636 | (keyword (intern-soft word-image))) | ||
| 637 | (when (and (or keyword (string= "nil" word-image)) | ||
| 638 | (delphi-is keyword delphi-keywords)) | ||
| 639 | (delphi-set-token-kind word keyword)) | ||
| 640 | word)))) | ||
| 641 | |||
| 642 | (defun delphi-explicit-token-at (p token-string kind) | ||
| 643 | ;; If point p is anywhere in the token string then returns the resulting | ||
| 644 | ;; token. | ||
| 645 | (let ((token (delphi-charset-token-at p token-string kind))) | ||
| 646 | (when (and token (string= token-string (delphi-token-string token))) | ||
| 647 | token))) | ||
| 648 | |||
| 649 | (defun delphi-token-at (p) | ||
| 650 | ;; Returns the token from parsing text at point p. | ||
| 651 | (when (and (<= (point-min) p) (<= p (point-max))) | ||
| 652 | (cond ((delphi-literal-token-at p)) | ||
| 653 | |||
| 654 | ((delphi-space-token-at p)) | ||
| 655 | |||
| 656 | ((delphi-word-token-at p)) | ||
| 657 | |||
| 658 | ((delphi-char-token-at p ?\( 'open-group)) | ||
| 659 | ((delphi-char-token-at p ?\) 'close-group)) | ||
| 660 | ((delphi-char-token-at p ?\[ 'open-group)) | ||
| 661 | ((delphi-char-token-at p ?\] 'close-group)) | ||
| 662 | ((delphi-char-token-at p ?\n 'newline)) | ||
| 663 | ((delphi-char-token-at p ?\; 'semicolon)) | ||
| 664 | ((delphi-char-token-at p ?. 'dot)) | ||
| 665 | ((delphi-char-token-at p ?, 'comma)) | ||
| 666 | ((delphi-char-token-at p ?= 'equals)) | ||
| 667 | ((delphi-char-token-at p ?+ 'plus)) | ||
| 668 | ((delphi-char-token-at p ?- 'minus)) | ||
| 669 | ((delphi-char-token-at p ?* 'times)) | ||
| 670 | ((delphi-char-token-at p ?/ 'divides)) | ||
| 671 | ((delphi-char-token-at p ?: 'colon)) | ||
| 672 | |||
| 673 | ((delphi-explicit-token-at p "<>" 'not-equals)) | ||
| 674 | |||
| 675 | ((delphi-point-token-at p 'punctuation))))) | ||
| 676 | |||
| 677 | (defun delphi-current-token () | ||
| 678 | ;; Returns the delphi source token under the current point. | ||
| 679 | (delphi-token-at (point))) | ||
| 680 | |||
| 681 | (defun delphi-next-token (token) | ||
| 682 | ;; Returns the token after the specified token. | ||
| 683 | (when token | ||
| 684 | (let ((next (delphi-token-at (delphi-token-end token)))) | ||
| 685 | (if next | ||
| 686 | (delphi-step-progress (delphi-token-start next) "Scanning" | ||
| 687 | delphi-scanning-progress-step)) | ||
| 688 | next))) | ||
| 689 | |||
| 690 | (defun delphi-previous-token (token) | ||
| 691 | ;; Returns the token before the specified token. | ||
| 692 | (when token | ||
| 693 | (let ((previous (delphi-token-at (1- (delphi-token-start token))))) | ||
| 694 | (if previous | ||
| 695 | (delphi-step-progress (delphi-token-start previous) "Scanning" | ||
| 696 | delphi-scanning-progress-step)) | ||
| 697 | previous))) | ||
| 698 | |||
| 699 | (defun delphi-next-visible-token (token) | ||
| 700 | ;; Returns the first non-space token after the specified token. | ||
| 701 | (let (next-token) | ||
| 702 | (while (progn | ||
| 703 | (setq next-token (delphi-next-token token)) | ||
| 704 | (delphi-is (delphi-token-kind next-token) '(space newline)))) | ||
| 705 | next-token)) | ||
| 706 | |||
| 707 | (defun delphi-parse-region (from to) | ||
| 708 | ;; Parses the literal tokens in the region. The point is set to "to". | ||
| 709 | (save-restriction | ||
| 710 | (widen) | ||
| 711 | (goto-char from) | ||
| 712 | (while (< (point) to) | ||
| 713 | (delphi-parse-next-literal to)))) | ||
| 714 | |||
| 715 | (defun delphi-parse-region-until-stable (from to) | ||
| 716 | ;; Parses at least the literal tokens in the region. After that, parsing | ||
| 717 | ;; continues as long as obsolete literal regions are encountered. The point | ||
| 718 | ;; is set to the encountered stable point. | ||
| 719 | (save-restriction | ||
| 720 | (widen) | ||
| 721 | (delphi-parse-region from to) | ||
| 722 | (while (not (delphi-is-stable-literal (point))) | ||
| 723 | (delphi-parse-next-literal (point-max))))) | ||
| 724 | |||
| 725 | (defun delphi-fontify-region (from to &optional verbose) | ||
| 726 | ;; Colors the text in the region according to Delphi rules. | ||
| 727 | (delphi-save-excursion | ||
| 728 | (delphi-save-state | ||
| 729 | (let ((p from) | ||
| 730 | (delphi-verbose verbose) | ||
| 731 | (token nil)) | ||
| 732 | (delphi-progress-start) | ||
| 733 | (while (< p to) | ||
| 734 | ;; Color the token and move past it. | ||
| 735 | (setq token (delphi-token-at p)) | ||
| 736 | (add-text-properties | ||
| 737 | (delphi-token-start token) (delphi-token-end token) | ||
| 738 | (list 'face (delphi-face-of (delphi-token-kind token)) 'lazy-lock t)) | ||
| 739 | (setq p (delphi-token-end token)) | ||
| 740 | (delphi-step-progress p "Fontifying" delphi-fontifying-progress-step)) | ||
| 741 | (delphi-progress-done))))) | ||
| 742 | |||
| 743 | (defconst delphi-ignore-changes t | ||
| 744 | "Internal flag to control if the delphi-mode responds to buffer changes. | ||
| 745 | Defaults to t in case the delphi-after-change function is called on a | ||
| 746 | non-delphi buffer. Set to nil in a delphi buffer. To override, just do: | ||
| 747 | (let ((delphi-ignore-changes t)) ...)") | ||
| 748 | |||
| 749 | (defun delphi-after-change (change-start change-end old-length) | ||
| 750 | ;; Called when the buffer has changed. Reparses the changed region. | ||
| 751 | (unless delphi-ignore-changes | ||
| 752 | (let ((delphi-ignore-changes t)) ; Prevent recursive calls. | ||
| 753 | (delphi-save-excursion | ||
| 754 | (delphi-progress-start) | ||
| 755 | ;; Reparse at least from the token previous to the change to the end of | ||
| 756 | ;; line after the change. | ||
| 757 | (delphi-parse-region-until-stable | ||
| 758 | (delphi-token-start (delphi-token-at (1- change-start))) | ||
| 759 | (progn (goto-char change-end) (end-of-line) (point))) | ||
| 760 | (delphi-progress-done))))) | ||
| 761 | |||
| 762 | (defun delphi-group-start (from-token) | ||
| 763 | ;; Returns the token that denotes the start of the ()/[] group. | ||
| 764 | (let ((token (delphi-previous-token from-token)) | ||
| 765 | (token-kind nil)) | ||
| 766 | (catch 'done | ||
| 767 | (while token | ||
| 768 | (setq token-kind (delphi-token-kind token)) | ||
| 769 | (cond | ||
| 770 | ;; Skip over nested groups. | ||
| 771 | ((eq 'close-group token-kind) (setq token (delphi-group-start token))) | ||
| 772 | ((eq 'open-group token-kind) (throw 'done token))) | ||
| 773 | (setq token (delphi-previous-token token))) | ||
| 774 | ;; Start not found. | ||
| 775 | nil))) | ||
| 776 | |||
| 777 | (defun delphi-group-end (from-token) | ||
| 778 | ;; Returns the token that denotes the end of the ()/[] group. | ||
| 779 | (let ((token (delphi-next-token from-token)) | ||
| 780 | (token-kind nil)) | ||
| 781 | (catch 'done | ||
| 782 | (while token | ||
| 783 | (setq token-kind (delphi-token-kind token)) | ||
| 784 | (cond | ||
| 785 | ;; Skip over nested groups. | ||
| 786 | ((eq 'open-group token-kind) (setq token (delphi-group-end token))) | ||
| 787 | ((eq 'close-group token-kind) (throw 'done token))) | ||
| 788 | (setq token (delphi-next-token token))) | ||
| 789 | ;; end not found. | ||
| 790 | nil))) | ||
| 791 | |||
| 792 | (defun delphi-indent-of (token &optional offset) | ||
| 793 | ;; Returns the start column of the token, plus any offset. | ||
| 794 | (let ((indent (+ (delphi-column-of (delphi-token-start token)) | ||
| 795 | (if offset offset 0)))) | ||
| 796 | (when delphi-debug | ||
| 797 | (delphi-debug-log | ||
| 798 | (concat "\n Indent of: %S %S" | ||
| 799 | "\n column: %d indent: %d offset: %d") | ||
| 800 | token (delphi-token-string token) | ||
| 801 | (delphi-column-of (delphi-token-start token)) | ||
| 802 | indent (if offset offset 0))) | ||
| 803 | indent)) | ||
| 804 | |||
| 805 | (defun delphi-line-indent-of (from-token &optional offset &rest terminators) | ||
| 806 | ;; Returns the column of first non-space character on the token's line, plus | ||
| 807 | ;; any offset. We also stop if one of the terminators or an open ( or [ is | ||
| 808 | ;; encountered. | ||
| 809 | (let ((token (delphi-previous-token from-token)) | ||
| 810 | (last-token from-token) | ||
| 811 | (kind nil)) | ||
| 812 | (catch 'done | ||
| 813 | (while token | ||
| 814 | (setq kind (delphi-token-kind token)) | ||
| 815 | (cond | ||
| 816 | ;; Skip over ()/[] groups. | ||
| 817 | ((eq 'close-group kind) (setq token (delphi-group-start token))) | ||
| 818 | |||
| 819 | ;; Stop at the beginning of the line or an open group. | ||
| 820 | ((delphi-is kind '(newline open-group)) (throw 'done nil)) | ||
| 821 | |||
| 822 | ;; Stop at one of the specified terminators. | ||
| 823 | ((delphi-is kind terminators) (throw 'done nil))) | ||
| 824 | (unless (delphi-is kind delphi-whitespace) (setq last-token token)) | ||
| 825 | (setq token (delphi-previous-token token)))) | ||
| 826 | (delphi-indent-of last-token offset))) | ||
| 827 | |||
| 828 | (defun delphi-stmt-line-indent-of (from-token &optional offset) | ||
| 829 | ;; Like `delphi-line-indent-of' except is also stops on a use clause or ":". | ||
| 830 | (apply #'delphi-line-indent-of | ||
| 831 | from-token offset `(;colon | ||
| 832 | ,@delphi-use-clauses))) | ||
| 833 | |||
| 834 | (defun delphi-open-group-indent (token last-token &optional offset) | ||
| 835 | ;; Returns the indent relative to an unmatched ( or [. | ||
| 836 | (when (eq 'open-group (delphi-token-kind token)) | ||
| 837 | (if last-token | ||
| 838 | (delphi-indent-of last-token offset) | ||
| 839 | ;; There is nothing following the ( or [. Indent from its line. | ||
| 840 | (delphi-stmt-line-indent-of token delphi-indent-level)))) | ||
| 841 | |||
| 842 | (defun delphi-composite-type-start (token last-token) | ||
| 843 | ;; Returns true (actually the last-token) if the pair equals (= class) or (= | ||
| 844 | ;; record), and nil otherwise. | ||
| 845 | (if (and (eq 'equals (delphi-token-kind token)) | ||
| 846 | (delphi-is (delphi-token-kind last-token) delphi-composite-types)) | ||
| 847 | last-token)) | ||
| 848 | |||
| 849 | (defun delphi-is-simple-class-type (at-token limit-token) | ||
| 850 | ;; True if at-token is the start of a simple class type. E.g. | ||
| 851 | ;; class of TClass; | ||
| 852 | ;; class (TBaseClass); | ||
| 853 | ;; class; | ||
| 854 | (when (delphi-is (delphi-token-kind at-token) delphi-class-types) | ||
| 855 | (catch 'done | ||
| 856 | ;; Scan until the semi colon. | ||
| 857 | (let ((token (delphi-next-token at-token)) | ||
| 858 | (token-kind nil) | ||
| 859 | (limit (delphi-token-start limit-token))) | ||
| 860 | (while (and token (<= (delphi-token-start token) limit)) | ||
| 861 | (setq token-kind (delphi-token-kind token)) | ||
| 862 | (cond | ||
| 863 | ;; A semicolon delimits the search. | ||
| 864 | ((eq 'semicolon token-kind) (throw 'done token)) | ||
| 865 | |||
| 866 | ;; Skip over the inheritance list. | ||
| 867 | ((eq 'open-group token-kind) (setq token (delphi-group-end token))) | ||
| 868 | |||
| 869 | ;; Only allow "of" and whitespace, and an identifier | ||
| 870 | ((delphi-is token-kind `(of word ,@delphi-whitespace))) | ||
| 871 | |||
| 872 | ;; Otherwise we are not in a simple class declaration. | ||
| 873 | ((throw 'done nil))) | ||
| 874 | (setq token (delphi-next-token token))))))) | ||
| 875 | |||
| 876 | (defun delphi-block-start (from-token &optional stop-on-class) | ||
| 877 | ;; Returns the token that denotes the start of the block. | ||
| 878 | (let ((token (delphi-previous-token from-token)) | ||
| 879 | (last-token nil) | ||
| 880 | (token-kind nil)) | ||
| 881 | (catch 'done | ||
| 882 | (while token | ||
| 883 | (setq token-kind (delphi-token-kind token)) | ||
| 884 | (cond | ||
| 885 | ;; Skip over nested blocks. | ||
| 886 | ((delphi-is token-kind delphi-end-block-statements) | ||
| 887 | (setq token (delphi-block-start token))) | ||
| 888 | |||
| 889 | ;; Regular block start found. | ||
| 890 | ((delphi-is token-kind delphi-block-statements) (throw 'done token)) | ||
| 891 | |||
| 892 | ;; A class/record start also begins a block. | ||
| 893 | ((delphi-composite-type-start token last-token) | ||
| 894 | (throw 'done (if stop-on-class last-token token))) | ||
| 895 | ) | ||
| 896 | (unless (delphi-is token-kind delphi-whitespace) | ||
| 897 | (setq last-token token)) | ||
| 898 | (setq token (delphi-previous-token token))) | ||
| 899 | ;; Start not found. | ||
| 900 | nil))) | ||
| 901 | |||
| 902 | (defun delphi-else-start (from-else) | ||
| 903 | ;; Returns the token of the if or case statement. | ||
| 904 | (let ((token (delphi-previous-token from-else)) | ||
| 905 | (token-kind nil) | ||
| 906 | (semicolon-count 0) | ||
| 907 | (if-count 0)) | ||
| 908 | (catch 'done | ||
| 909 | (while token | ||
| 910 | (setq token-kind (delphi-token-kind token)) | ||
| 911 | (cond | ||
| 912 | ;; Skip over nested groups. | ||
| 913 | ((eq 'close-group token-kind) (setq token (delphi-group-start token))) | ||
| 914 | |||
| 915 | ;; Skip over any nested blocks. | ||
| 916 | ((delphi-is token-kind delphi-end-block-statements) | ||
| 917 | (setq token (delphi-block-start token))) | ||
| 918 | |||
| 919 | ((eq 'semicolon token-kind) | ||
| 920 | ;; Semicolon means we are looking for an enclosing if, unless we | ||
| 921 | ;; are in a case statement. Keep counts of the semicolons and decide | ||
| 922 | ;; later. | ||
| 923 | (setq semicolon-count (1+ semicolon-count))) | ||
| 924 | |||
| 925 | ((and (eq 'if token-kind) (= semicolon-count 0)) | ||
| 926 | ;; We only can match an if when there have been no intervening | ||
| 927 | ;; semicolons. | ||
| 928 | (throw 'done token)) | ||
| 929 | |||
| 930 | ((eq 'case token-kind) | ||
| 931 | ;; We have hit a case statement start. | ||
| 932 | (throw 'done token))) | ||
| 933 | (setq token (delphi-previous-token token))) | ||
| 934 | ;; No if or case statement found. | ||
| 935 | nil))) | ||
| 936 | |||
| 937 | (defun delphi-comment-content-start (comment) | ||
| 938 | ;; Returns the point of the first non-space character in the comment. | ||
| 939 | (let ((kind (delphi-token-kind comment))) | ||
| 940 | (when (delphi-is kind delphi-comments) | ||
| 941 | (delphi-save-excursion | ||
| 942 | (goto-char (+ (delphi-token-start comment) | ||
| 943 | (length (delphi-literal-start-pattern kind)))) | ||
| 944 | (skip-chars-forward delphi-space-chars) | ||
| 945 | (point))))) | ||
| 946 | |||
| 947 | (defun delphi-comment-block-start (comment) | ||
| 948 | ;; Returns the starting comment token of a contiguous // comment block. If | ||
| 949 | ;; the comment is multiline (i.e. {...} or (*...*)), the original comment is | ||
| 950 | ;; returned. | ||
| 951 | (if (not (eq 'comment-single-line (delphi-token-kind comment))) | ||
| 952 | comment | ||
| 953 | ;; Scan until we run out of // comments. | ||
| 954 | (let ((prev-comment comment) | ||
| 955 | (start-comment comment) | ||
| 956 | (kind nil)) | ||
| 957 | (while (let ((kind (delphi-token-kind prev-comment))) | ||
| 958 | (cond ((eq kind 'space)) | ||
| 959 | ((eq kind 'comment-single-line) | ||
| 960 | (setq start-comment prev-comment)) | ||
| 961 | (t nil))) | ||
| 962 | (setq prev-comment (delphi-previous-token prev-comment))) | ||
| 963 | start-comment))) | ||
| 964 | |||
| 965 | (defun delphi-comment-block-end (comment) | ||
| 966 | ;; Returns the end comment token of a contiguous // comment block. If the | ||
| 967 | ;; comment is multiline (i.e. {...} or (*...*)), the original comment is | ||
| 968 | ;; returned. | ||
| 969 | (if (not (eq 'comment-single-line (delphi-token-kind comment))) | ||
| 970 | comment | ||
| 971 | ;; Scan until we run out of // comments. | ||
| 972 | (let ((next-comment comment) | ||
| 973 | (end-comment comment) | ||
| 974 | (kind nil)) | ||
| 975 | (while (let ((kind (delphi-token-kind next-comment))) | ||
| 976 | (cond ((eq kind 'space)) | ||
| 977 | ((eq kind 'comment-single-line) | ||
| 978 | (setq end-comment next-comment)) | ||
| 979 | (t nil))) | ||
| 980 | (setq next-comment (delphi-next-token next-comment))) | ||
| 981 | end-comment))) | ||
| 982 | |||
| 983 | (defun delphi-on-first-comment-line (comment) | ||
| 984 | ;; Returns true if the current point is on the first line of the comment. | ||
| 985 | (save-excursion | ||
| 986 | (let ((comment-start (delphi-token-start comment)) | ||
| 987 | (current-point (point))) | ||
| 988 | (goto-char comment-start) | ||
| 989 | (end-of-line) | ||
| 990 | (and (<= comment-start current-point) (<= current-point (point)))))) | ||
| 991 | |||
| 992 | (defun delphi-comment-indent-of (comment) | ||
| 993 | ;; Returns the correct indentation for the comment. | ||
| 994 | (let ((start-comment (delphi-comment-block-start comment))) | ||
| 995 | (if (and (eq start-comment comment) | ||
| 996 | (delphi-on-first-comment-line comment)) | ||
| 997 | ;; Indent as a statement. | ||
| 998 | (delphi-enclosing-indent-of comment) | ||
| 999 | (save-excursion | ||
| 1000 | (let ((kind (delphi-token-kind comment))) | ||
| 1001 | (beginning-of-line) | ||
| 1002 | (cond ((eq 'comment-single-line kind) | ||
| 1003 | ;; Indent to the first comment in the // block. | ||
| 1004 | (delphi-indent-of start-comment)) | ||
| 1005 | |||
| 1006 | ((looking-at (concat delphi-leading-spaces-re | ||
| 1007 | (delphi-literal-stop-pattern kind))) | ||
| 1008 | ;; Indent multi-line comment terminators to the comment start. | ||
| 1009 | (delphi-indent-of comment)) | ||
| 1010 | |||
| 1011 | ;; Indent according to the comment's content start. | ||
| 1012 | ((delphi-column-of (delphi-comment-content-start comment))))))) | ||
| 1013 | )) | ||
| 1014 | |||
| 1015 | (defun delphi-is-use-clause-end (at-token last-token last-colon from-kind) | ||
| 1016 | ;; True if we are after the end of a uses type clause. | ||
| 1017 | (when (and last-token | ||
| 1018 | (not last-colon) | ||
| 1019 | (eq 'comma (delphi-token-kind at-token)) | ||
| 1020 | (eq 'semicolon from-kind)) | ||
| 1021 | ;; Scan for the uses statement, just to be sure. | ||
| 1022 | (let ((token (delphi-previous-token at-token)) | ||
| 1023 | (token-kind nil)) | ||
| 1024 | (catch 'done | ||
| 1025 | (while token | ||
| 1026 | (setq token-kind (delphi-token-kind token)) | ||
| 1027 | (cond ((delphi-is token-kind delphi-use-clauses) | ||
| 1028 | (throw 'done t)) | ||
| 1029 | |||
| 1030 | ;; Whitespace, identifiers, strings, "in" keyword, and commas | ||
| 1031 | ;; are allowed in use clauses. | ||
| 1032 | ((or (delphi-is token-kind '(word comma in newline)) | ||
| 1033 | (delphi-is token-kind delphi-whitespace) | ||
| 1034 | (delphi-is token-kind delphi-strings))) | ||
| 1035 | |||
| 1036 | ;; Nothing else is. | ||
| 1037 | ((throw 'done nil))) | ||
| 1038 | (setq token (delphi-previous-token token))) | ||
| 1039 | nil)))) | ||
| 1040 | |||
| 1041 | (defun delphi-is-block-after-expr-statement (token) | ||
| 1042 | ;; Returns true if we have a block token trailing an expression delimiter (of | ||
| 1043 | ;; presumably an expression statement). | ||
| 1044 | (when (delphi-is (delphi-token-kind token) delphi-block-statements) | ||
| 1045 | (let ((previous (delphi-previous-token token)) | ||
| 1046 | (previous-kind nil)) | ||
| 1047 | (while (progn | ||
| 1048 | (setq previous-kind (delphi-token-kind previous)) | ||
| 1049 | (eq previous-kind 'space)) | ||
| 1050 | (setq previous (delphi-previous-token previous))) | ||
| 1051 | (or (delphi-is previous-kind delphi-expr-delimiters) | ||
| 1052 | (eq previous-kind 'else))))) | ||
| 1053 | |||
| 1054 | (defun delphi-previous-indent-of (from-token) | ||
| 1055 | ;; Returns the indentation of the previous statement of the token. | ||
| 1056 | (let ((token (delphi-previous-token from-token)) | ||
| 1057 | (token-kind nil) | ||
| 1058 | (from-kind (delphi-token-kind from-token)) | ||
| 1059 | (last-colon nil) | ||
| 1060 | (last-token nil)) | ||
| 1061 | (catch 'done | ||
| 1062 | (while token | ||
| 1063 | (setq token-kind (delphi-token-kind token)) | ||
| 1064 | (cond | ||
| 1065 | ;; An open ( or [ always is an indent point. | ||
| 1066 | ((eq 'open-group token-kind) | ||
| 1067 | (throw 'done (delphi-open-group-indent token last-token))) | ||
| 1068 | |||
| 1069 | ;; Skip over any ()/[] groups. | ||
| 1070 | ((eq 'close-group token-kind) (setq token (delphi-group-start token))) | ||
| 1071 | |||
| 1072 | ((delphi-is token-kind delphi-end-block-statements) | ||
| 1073 | (if (eq 'newline (delphi-token-kind (delphi-previous-token token))) | ||
| 1074 | ;; We can stop at an end token that is right up against the | ||
| 1075 | ;; margin. | ||
| 1076 | (throw 'done 0) | ||
| 1077 | ;; Otherwise, skip over any nested blocks. | ||
| 1078 | (setq token (delphi-block-start token)))) | ||
| 1079 | |||
| 1080 | ;; Special case: if we encounter a ", word;" then we assume that we | ||
| 1081 | ;; are in some kind of uses clause, and thus indent to column 0. This | ||
| 1082 | ;; works because no other constructs are known to have that form. | ||
| 1083 | ;; This fixes the irritating case of having indents after a uses | ||
| 1084 | ;; clause look like: | ||
| 1085 | ;; uses | ||
| 1086 | ;; someUnit, | ||
| 1087 | ;; someOtherUnit; | ||
| 1088 | ;; // this should be at column 0! | ||
| 1089 | ((delphi-is-use-clause-end token last-token last-colon from-kind) | ||
| 1090 | (throw 'done 0)) | ||
| 1091 | |||
| 1092 | ;; A previous terminator means we can stop. If we are on a directive, | ||
| 1093 | ;; however, then we are not actually encountering a new statement. | ||
| 1094 | ((and last-token | ||
| 1095 | (delphi-is token-kind delphi-previous-terminators) | ||
| 1096 | (not (delphi-is (delphi-token-kind last-token) | ||
| 1097 | delphi-directives))) | ||
| 1098 | (throw 'done (delphi-stmt-line-indent-of last-token 0))) | ||
| 1099 | |||
| 1100 | ;; Ignore whitespace. | ||
| 1101 | ((delphi-is token-kind delphi-whitespace)) | ||
| 1102 | |||
| 1103 | ;; Remember any ':' we encounter, since that affects how we indent to | ||
| 1104 | ;; a case statement. | ||
| 1105 | ((eq 'colon token-kind) (setq last-colon token)) | ||
| 1106 | |||
| 1107 | ;; A case statement delimits a previous statement. We indent labels | ||
| 1108 | ;; specially. | ||
| 1109 | ((eq 'case token-kind) | ||
| 1110 | (throw 'done | ||
| 1111 | (if last-colon (delphi-line-indent-of last-colon) | ||
| 1112 | (delphi-line-indent-of token delphi-case-label-indent)))) | ||
| 1113 | |||
| 1114 | ;; If we are in a use clause then commas mark an enclosing rather than | ||
| 1115 | ;; a previous statement. | ||
| 1116 | ((delphi-is token-kind delphi-use-clauses) | ||
| 1117 | (throw 'done | ||
| 1118 | (if (eq 'comma from-kind) | ||
| 1119 | (if last-token | ||
| 1120 | ;; Indent to first unit in use clause. | ||
| 1121 | (delphi-indent-of last-token) | ||
| 1122 | ;; Indent from use clause keyword. | ||
| 1123 | (delphi-line-indent-of token delphi-indent-level)) | ||
| 1124 | ;; Indent to use clause keyword. | ||
| 1125 | (delphi-line-indent-of token)))) | ||
| 1126 | |||
| 1127 | ;; An enclosing statement delimits a previous statement. | ||
| 1128 | ;; We try to use the existing indent of the previous statement, | ||
| 1129 | ;; otherwise we calculate from the enclosing statement. | ||
| 1130 | ((delphi-is token-kind delphi-previous-enclosing-statements) | ||
| 1131 | (throw 'done (if last-token (delphi-line-indent-of last-token) | ||
| 1132 | (delphi-line-indent-of token delphi-indent-level)))) | ||
| 1133 | |||
| 1134 | ;; A class or record declaration also delimits a previous statement. | ||
| 1135 | ((delphi-composite-type-start token last-token) | ||
| 1136 | (throw | ||
| 1137 | 'done | ||
| 1138 | (if (delphi-is-simple-class-type last-token from-token) | ||
| 1139 | ;; c = class; or c = class of T; are previous statements. | ||
| 1140 | (delphi-line-indent-of token) | ||
| 1141 | ;; Otherwise c = class ... or r = record ... are enclosing | ||
| 1142 | ;; statements. | ||
| 1143 | (delphi-line-indent-of last-token delphi-indent-level)))) | ||
| 1144 | |||
| 1145 | ;; We have a definite previous statement delimiter. | ||
| 1146 | ((delphi-is token-kind delphi-previous-statements) | ||
| 1147 | (throw 'done (delphi-stmt-line-indent-of token 0))) | ||
| 1148 | ) | ||
| 1149 | (unless (delphi-is token-kind delphi-whitespace) | ||
| 1150 | (setq last-token token)) | ||
| 1151 | (setq token (delphi-previous-token token))) | ||
| 1152 | ;; We ran out of tokens. Indent to column 0. | ||
| 1153 | 0))) | ||
| 1154 | |||
| 1155 | (defun delphi-section-indent-of (section-token) | ||
| 1156 | ;; Returns the indentation appropriate for begin/var/const/type/label | ||
| 1157 | ;; tokens. | ||
| 1158 | (let* ((token (delphi-previous-token section-token)) | ||
| 1159 | (token-kind nil) | ||
| 1160 | (last-token nil) | ||
| 1161 | (nested-block-count 0) | ||
| 1162 | (expr-delimited nil) | ||
| 1163 | (last-terminator nil)) | ||
| 1164 | (catch 'done | ||
| 1165 | (while token | ||
| 1166 | (setq token-kind (delphi-token-kind token)) | ||
| 1167 | (cond | ||
| 1168 | ;; Always stop at unmatched ( or [. | ||
| 1169 | ((eq token-kind 'open-group) | ||
| 1170 | (throw 'done (delphi-open-group-indent token last-token))) | ||
| 1171 | |||
| 1172 | ;; Skip over any ()/[] groups. | ||
| 1173 | ((eq 'close-group token-kind) (setq token (delphi-group-start token))) | ||
| 1174 | |||
| 1175 | ((delphi-is token-kind delphi-end-block-statements) | ||
| 1176 | (if (eq 'newline (delphi-token-kind (delphi-previous-token token))) | ||
| 1177 | ;; We can stop at an end token that is right up against the | ||
| 1178 | ;; margin. | ||
| 1179 | (throw 'done 0) | ||
| 1180 | ;; Otherwise, skip over any nested blocks. | ||
| 1181 | (setq token (delphi-block-start token) | ||
| 1182 | nested-block-count (1+ nested-block-count)))) | ||
| 1183 | |||
| 1184 | ;; Remember if we have encountered any forward routine declarations. | ||
| 1185 | ((eq 'forward token-kind) | ||
| 1186 | (setq nested-block-count (1+ nested-block-count))) | ||
| 1187 | |||
| 1188 | ;; Mark the completion of a nested routine traversal. | ||
| 1189 | ((and (delphi-is token-kind delphi-routine-statements) | ||
| 1190 | (> nested-block-count 0)) | ||
| 1191 | (setq nested-block-count (1- nested-block-count))) | ||
| 1192 | |||
| 1193 | ;; Remember if we have encountered any statement terminators. | ||
| 1194 | ((eq 'semicolon token-kind) (setq last-terminator token)) | ||
| 1195 | |||
| 1196 | ;; Remember if we have encountered any expression delimiters. | ||
| 1197 | ((delphi-is token-kind delphi-expr-delimiters) | ||
| 1198 | (setq expr-delimited token)) | ||
| 1199 | |||
| 1200 | ;; Enclosing body statements are delimiting. We indent the compound | ||
| 1201 | ;; bodies specially. | ||
| 1202 | ((and (not last-terminator) | ||
| 1203 | (delphi-is token-kind delphi-body-statements)) | ||
| 1204 | (throw 'done | ||
| 1205 | (delphi-stmt-line-indent-of token delphi-compound-block-indent))) | ||
| 1206 | |||
| 1207 | ;; An enclosing ":" means a label. | ||
| 1208 | ((and (eq 'colon token-kind) | ||
| 1209 | (delphi-is (delphi-token-kind section-token) | ||
| 1210 | delphi-block-statements) | ||
| 1211 | (not last-terminator) | ||
| 1212 | (not expr-delimited) | ||
| 1213 | (not (eq 'equals (delphi-token-kind last-token)))) | ||
| 1214 | (throw 'done | ||
| 1215 | (delphi-stmt-line-indent-of token delphi-indent-level))) | ||
| 1216 | |||
| 1217 | ;; Block and mid block tokens are always enclosing | ||
| 1218 | ((delphi-is token-kind delphi-begin-enclosing-tokens) | ||
| 1219 | (throw 'done | ||
| 1220 | (delphi-line-indent-of token delphi-indent-level))) | ||
| 1221 | |||
| 1222 | ;; Declaration sections and routines are delimiters, unless they | ||
| 1223 | ;; are part of a nested routine. | ||
| 1224 | ((and (delphi-is token-kind delphi-decl-delimiters) | ||
| 1225 | (= 0 nested-block-count)) | ||
| 1226 | (throw 'done (delphi-line-indent-of token 0))) | ||
| 1227 | |||
| 1228 | ;; Unit statements mean we indent right to the left. | ||
| 1229 | ((delphi-is token-kind delphi-unit-statements) (throw 'done 0)) | ||
| 1230 | ) | ||
| 1231 | (unless (delphi-is token-kind delphi-whitespace) | ||
| 1232 | (setq last-token token)) | ||
| 1233 | (setq token (delphi-previous-token token))) | ||
| 1234 | ;; We ran out of tokens. Indent to column 0. | ||
| 1235 | 0))) | ||
| 1236 | |||
| 1237 | (defun delphi-enclosing-indent-of (from-token) | ||
| 1238 | ;; Returns the indentation offset from the enclosing statement of the token. | ||
| 1239 | (let ((token (delphi-previous-token from-token)) | ||
| 1240 | (from-kind (delphi-token-kind from-token)) | ||
| 1241 | (token-kind nil) | ||
| 1242 | (stmt-start nil) | ||
| 1243 | (last-token nil) | ||
| 1244 | (equals-encountered nil) | ||
| 1245 | (before-equals nil) | ||
| 1246 | (expr-delimited nil)) | ||
| 1247 | (catch 'done | ||
| 1248 | (while token | ||
| 1249 | (setq token-kind (delphi-token-kind token)) | ||
| 1250 | (cond | ||
| 1251 | ;; An open ( or [ always is an indent point. | ||
| 1252 | ((eq 'open-group token-kind) | ||
| 1253 | (throw 'done | ||
| 1254 | (delphi-open-group-indent | ||
| 1255 | token last-token | ||
| 1256 | (if (delphi-is from-kind delphi-binary-ops) | ||
| 1257 | ;; Keep binary operations aligned with the open group. | ||
| 1258 | 0 | ||
| 1259 | delphi-indent-level)))) | ||
| 1260 | |||
| 1261 | ;; Skip over any ()/[] groups. | ||
| 1262 | ((eq 'close-group token-kind) (setq token (delphi-group-start token))) | ||
| 1263 | |||
| 1264 | ;; Skip over any nested blocks. | ||
| 1265 | ((delphi-is token-kind delphi-end-block-statements) | ||
| 1266 | (setq token (delphi-block-start token))) | ||
| 1267 | |||
| 1268 | ;; An expression delimiter affects indentation depending on whether | ||
| 1269 | ;; the point is before or after it. Remember that we encountered one. | ||
| 1270 | ;; Also remember the last encountered token, since if it exists it | ||
| 1271 | ;; should be the actual indent point. | ||
| 1272 | ((delphi-is token-kind delphi-expr-delimiters) | ||
| 1273 | (setq expr-delimited token stmt-start last-token)) | ||
| 1274 | |||
| 1275 | ;; With a non-delimited expression statement we indent after the | ||
| 1276 | ;; statement's keyword, unless we are on the delimiter itself. | ||
| 1277 | ((and (not expr-delimited) | ||
| 1278 | (delphi-is token-kind delphi-expr-statements)) | ||
| 1279 | (throw 'done | ||
| 1280 | (cond ((delphi-is from-kind delphi-expr-delimiters) | ||
| 1281 | ;; We are indenting a delimiter. Indent to the statement. | ||
| 1282 | (delphi-stmt-line-indent-of token 0)) | ||
| 1283 | |||
| 1284 | ((and last-token (delphi-is from-kind delphi-binary-ops)) | ||
| 1285 | ;; Align binary ops with the expression. | ||
| 1286 | (delphi-indent-of last-token)) | ||
| 1287 | |||
| 1288 | (last-token | ||
| 1289 | ;; Indent in from the expression. | ||
| 1290 | (delphi-indent-of last-token delphi-indent-level)) | ||
| 1291 | |||
| 1292 | ;; Indent in from the statement's keyword. | ||
| 1293 | ((delphi-indent-of token delphi-indent-level))))) | ||
| 1294 | |||
| 1295 | ;; A delimited case statement indents the label according to | ||
| 1296 | ;; a special rule. | ||
| 1297 | ((eq 'case token-kind) | ||
| 1298 | (throw 'done | ||
| 1299 | (if stmt-start | ||
| 1300 | ;; We are not actually indenting to the case statement, | ||
| 1301 | ;; but are within a label expression. | ||
| 1302 | (delphi-stmt-line-indent-of | ||
| 1303 | stmt-start delphi-indent-level) | ||
| 1304 | ;; Indent from the case keyword. | ||
| 1305 | (delphi-stmt-line-indent-of | ||
| 1306 | token delphi-case-label-indent)))) | ||
| 1307 | |||
| 1308 | ;; Body expression statements are enclosing. Indent from the | ||
| 1309 | ;; statement's keyword, unless we have a non-block statement following | ||
| 1310 | ;; it. | ||
| 1311 | ((delphi-is token-kind delphi-body-expr-statements) | ||
| 1312 | (throw 'done | ||
| 1313 | (delphi-stmt-line-indent-of | ||
| 1314 | (or stmt-start token) delphi-indent-level))) | ||
| 1315 | |||
| 1316 | ;; An else statement is enclosing, but it doesn't have an expression. | ||
| 1317 | ;; Thus we take into account last-token instead of stmt-start. | ||
| 1318 | ((eq 'else token-kind) | ||
| 1319 | (throw 'done (delphi-stmt-line-indent-of | ||
| 1320 | (or last-token token) delphi-indent-level))) | ||
| 1321 | |||
| 1322 | ;; We indent relative to an enclosing declaration section. | ||
| 1323 | ((delphi-is token-kind delphi-decl-sections) | ||
| 1324 | (throw 'done (delphi-indent-of (if last-token last-token token) | ||
| 1325 | delphi-indent-level))) | ||
| 1326 | |||
| 1327 | ;; In unit sections we indent right to the left. | ||
| 1328 | ((delphi-is token-kind delphi-unit-sections) (throw 'done 0)) | ||
| 1329 | |||
| 1330 | ;; A previous terminator means we can stop. | ||
| 1331 | ((delphi-is token-kind delphi-previous-terminators) | ||
| 1332 | (throw 'done | ||
| 1333 | (cond ((and last-token | ||
| 1334 | (eq 'comma token-kind) | ||
| 1335 | (delphi-is from-kind delphi-binary-ops)) | ||
| 1336 | ;; Align binary ops with the expression. | ||
| 1337 | (delphi-indent-of last-token)) | ||
| 1338 | |||
| 1339 | (last-token | ||
| 1340 | ;; Indent in from the expression. | ||
| 1341 | (delphi-indent-of last-token delphi-indent-level)) | ||
| 1342 | |||
| 1343 | ;; No enclosing expression; use the previous statment's | ||
| 1344 | ;; indent. | ||
| 1345 | ((delphi-previous-indent-of token))))) | ||
| 1346 | |||
| 1347 | ;; A block statement after an expression delimiter has its start | ||
| 1348 | ;; column as the expression statement. E.g. | ||
| 1349 | ;; if (a = b) | ||
| 1350 | ;; and (a != c) then begin | ||
| 1351 | ;; //... | ||
| 1352 | ;; end; | ||
| 1353 | ;; Remember it for when we encounter the expression statement start. | ||
| 1354 | ((delphi-is-block-after-expr-statement token) | ||
| 1355 | (throw 'done | ||
| 1356 | (cond (last-token (delphi-indent-of last-token delphi-indent-level)) | ||
| 1357 | |||
| 1358 | ((+ (delphi-section-indent-of token) delphi-indent-level))))) | ||
| 1359 | |||
| 1360 | ;; Stop at an enclosing statement and indent from it. | ||
| 1361 | ((delphi-is token-kind delphi-enclosing-statements) | ||
| 1362 | (throw 'done (delphi-stmt-line-indent-of | ||
| 1363 | (or last-token token) delphi-indent-level))) | ||
| 1364 | |||
| 1365 | ;; A class/record declaration is also enclosing. | ||
| 1366 | ((delphi-composite-type-start token last-token) | ||
| 1367 | (throw 'done | ||
| 1368 | (delphi-line-indent-of last-token delphi-indent-level))) | ||
| 1369 | |||
| 1370 | ;; A ":" we indent relative to its line beginning. If we are in a | ||
| 1371 | ;; parameter list, then stop also if we hit a ";". | ||
| 1372 | ((and (eq token-kind 'colon) | ||
| 1373 | (not expr-delimited) | ||
| 1374 | (not (delphi-is from-kind delphi-expr-delimiters)) | ||
| 1375 | (not equals-encountered) | ||
| 1376 | (not (eq from-kind 'equals))) | ||
| 1377 | (throw 'done | ||
| 1378 | (if last-token | ||
| 1379 | (delphi-indent-of last-token delphi-indent-level) | ||
| 1380 | (delphi-line-indent-of token delphi-indent-level 'semicolon)))) | ||
| 1381 | |||
| 1382 | ;; If the ":" was not processed above and we have token after the "=", | ||
| 1383 | ;; then indent from the "=". Ignore :=, however. | ||
| 1384 | ((and (eq token-kind 'colon) equals-encountered before-equals) | ||
| 1385 | (cond | ||
| 1386 | ;; Ignore binary ops for now. It would do, for example: | ||
| 1387 | ;; val := 1 + 2 | ||
| 1388 | ;; + 3; | ||
| 1389 | ;; which is good, but also | ||
| 1390 | ;; val := Foo | ||
| 1391 | ;; (foo, args) | ||
| 1392 | ;; + 2; | ||
| 1393 | ;; which doesn't look right. | ||
| 1394 | ;;;; Align binary ops with the before token. | ||
| 1395 | ;;((delphi-is from-kind delphi-binary-ops) | ||
| 1396 | ;;(throw 'done (delphi-indent-of before-equals 0))) | ||
| 1397 | |||
| 1398 | ;; Assignments (:=) we skip over to get a normal indent. | ||
| 1399 | ((eq (delphi-token-kind last-token) 'equals)) | ||
| 1400 | |||
| 1401 | ;; Otherwise indent in from the equals. | ||
| 1402 | ((throw 'done | ||
| 1403 | (delphi-indent-of before-equals delphi-indent-level))))) | ||
| 1404 | |||
| 1405 | ;; Remember any "=" we encounter if it has not already been processed. | ||
| 1406 | ((eq token-kind 'equals) | ||
| 1407 | (setq equals-encountered token | ||
| 1408 | before-equals last-token)) | ||
| 1409 | ) | ||
| 1410 | (unless (delphi-is token-kind delphi-whitespace) | ||
| 1411 | (setq last-token token)) | ||
| 1412 | (setq token (delphi-previous-token token))) | ||
| 1413 | ;; We ran out of tokens. Indent to column 0. | ||
| 1414 | 0))) | ||
| 1415 | |||
| 1416 | (defun delphi-corrected-indentation () | ||
| 1417 | ;; Returns the corrected indentation for the current line. | ||
| 1418 | (delphi-save-excursion | ||
| 1419 | (delphi-progress-start) | ||
| 1420 | ;; Move to the first token on the line. | ||
| 1421 | (beginning-of-line) | ||
| 1422 | (skip-chars-forward delphi-space-chars) | ||
| 1423 | (let* ((token (delphi-current-token)) | ||
| 1424 | (token-kind (delphi-token-kind token)) | ||
| 1425 | (indent | ||
| 1426 | (cond ((eq 'close-group token-kind) | ||
| 1427 | ;; Indent to the matching start ( or [. | ||
| 1428 | (delphi-indent-of (delphi-group-start token))) | ||
| 1429 | |||
| 1430 | ((delphi-is token-kind delphi-unit-statements) 0) | ||
| 1431 | |||
| 1432 | ((delphi-is token-kind delphi-comments) | ||
| 1433 | ;; In a comment. | ||
| 1434 | (delphi-comment-indent-of token)) | ||
| 1435 | |||
| 1436 | ((delphi-is token-kind delphi-decl-matchers) | ||
| 1437 | ;; Use a previous section/routine's indent. | ||
| 1438 | (delphi-section-indent-of token)) | ||
| 1439 | |||
| 1440 | ((delphi-is token-kind delphi-match-block-statements) | ||
| 1441 | ;; Use the block's indentation. | ||
| 1442 | (let ((block-start | ||
| 1443 | (delphi-block-start token 'stop-on-class))) | ||
| 1444 | (cond | ||
| 1445 | ;; When trailing a body statement, indent to | ||
| 1446 | ;; the statement's keyword. | ||
| 1447 | ((delphi-is-block-after-expr-statement block-start) | ||
| 1448 | (delphi-section-indent-of block-start)) | ||
| 1449 | |||
| 1450 | ;; Otherwise just indent to the block start. | ||
| 1451 | ((delphi-stmt-line-indent-of block-start 0))))) | ||
| 1452 | |||
| 1453 | ((eq 'else token-kind) | ||
| 1454 | ;; Find the start of the if or case statement. | ||
| 1455 | (delphi-stmt-line-indent-of (delphi-else-start token) 0)) | ||
| 1456 | |||
| 1457 | ;; Otherwise indent in from enclosing statement. | ||
| 1458 | ((delphi-enclosing-indent-of | ||
| 1459 | (if token token (delphi-token-at (1- (point))))))))) | ||
| 1460 | (delphi-progress-done) | ||
| 1461 | indent))) | ||
| 1462 | |||
| 1463 | (defun delphi-indent-line () | ||
| 1464 | "Indent the current line according to the current language construct. If | ||
| 1465 | before the indent, the point is moved to the indent." | ||
| 1466 | (interactive) | ||
| 1467 | (delphi-save-match-data | ||
| 1468 | (let ((marked-point (point-marker)) ; Maintain our position reliably. | ||
| 1469 | (new-point nil) | ||
| 1470 | (line-start nil) | ||
| 1471 | (old-indent 0) | ||
| 1472 | (new-indent 0)) | ||
| 1473 | (beginning-of-line) | ||
| 1474 | (setq line-start (point)) | ||
| 1475 | (skip-chars-forward delphi-space-chars) | ||
| 1476 | (setq old-indent (current-column)) | ||
| 1477 | (setq new-indent (delphi-corrected-indentation)) | ||
| 1478 | (if (< marked-point (point)) | ||
| 1479 | ;; If before the indent column, then move to it. | ||
| 1480 | (set-marker marked-point (point))) | ||
| 1481 | ;; Advance our marked point after inserted spaces. | ||
| 1482 | (set-marker-insertion-type marked-point t) | ||
| 1483 | (when (/= old-indent new-indent) | ||
| 1484 | (delete-region line-start (point)) | ||
| 1485 | (insert (make-string new-indent ?\ ))) | ||
| 1486 | (goto-char marked-point) | ||
| 1487 | (set-marker marked-point nil)))) | ||
| 1488 | |||
| 1489 | (defvar delphi-mode-abbrev-table nil | ||
| 1490 | "Abbrev table in use in delphi-mode buffers.") | ||
| 1491 | (define-abbrev-table 'delphi-mode-abbrev-table ()) | ||
| 1492 | |||
| 1493 | (defmacro delphi-ensure-buffer (buffer-var buffer-name) | ||
| 1494 | ;; Ensures there exists a buffer of the specified name in the specified | ||
| 1495 | ;; variable. | ||
| 1496 | `(when (not (buffer-live-p ,buffer-var)) | ||
| 1497 | (setq ,buffer-var (get-buffer-create ,buffer-name)))) | ||
| 1498 | |||
| 1499 | (defun delphi-log-msg (to-buffer the-msg) | ||
| 1500 | ;; Writes a message to the end of the specified buffer. | ||
| 1501 | (with-current-buffer to-buffer | ||
| 1502 | (save-selected-window | ||
| 1503 | (switch-to-buffer-other-window to-buffer) | ||
| 1504 | (goto-char (point-max)) | ||
| 1505 | (set-window-dot (get-buffer-window to-buffer) (point)) | ||
| 1506 | (insert the-msg)))) | ||
| 1507 | |||
| 1508 | ;; Debugging helpers: | ||
| 1509 | |||
| 1510 | (defvar delphi-debug-buffer nil | ||
| 1511 | "Buffer to write delphi-mode debug messages to. Created on demand.") | ||
| 1512 | |||
| 1513 | (defun delphi-debug-log (format-string &rest args) | ||
| 1514 | ;; Writes a message to the log buffer. | ||
| 1515 | (when delphi-debug | ||
| 1516 | (delphi-ensure-buffer delphi-debug-buffer "*Delphi Debug Log*") | ||
| 1517 | (delphi-log-msg delphi-debug-buffer | ||
| 1518 | (concat (format-time-string "%H:%M:%S " (current-time)) | ||
| 1519 | (apply #'format (cons format-string args)) | ||
| 1520 | "\n")))) | ||
| 1521 | |||
| 1522 | (defun delphi-debug-token-string (token) | ||
| 1523 | (let* ((image (delphi-token-string token)) | ||
| 1524 | (has-newline (string-match "^\\([^\n]*\\)\n\\(.+\\)?$" image))) | ||
| 1525 | (when has-newline | ||
| 1526 | (setq image (concat (match-string 1 image) | ||
| 1527 | (if (match-beginning 2) "...")))) | ||
| 1528 | image)) | ||
| 1529 | |||
| 1530 | (defun delphi-debug-show-current-token () | ||
| 1531 | (interactive) | ||
| 1532 | (let ((token (delphi-current-token))) | ||
| 1533 | (delphi-debug-log "Token: %S %S" token (delphi-debug-token-string token)))) | ||
| 1534 | |||
| 1535 | (defun delphi-debug-goto-point (p) | ||
| 1536 | (interactive "NGoto char: ") | ||
| 1537 | (goto-char p)) | ||
| 1538 | |||
| 1539 | (defun delphi-debug-goto-next-token () | ||
| 1540 | (interactive) | ||
| 1541 | (goto-char (delphi-token-start (delphi-next-token (delphi-current-token))))) | ||
| 1542 | |||
| 1543 | (defun delphi-debug-goto-previous-token () | ||
| 1544 | (interactive) | ||
| 1545 | (goto-char | ||
| 1546 | (delphi-token-start (delphi-previous-token (delphi-current-token))))) | ||
| 1547 | |||
| 1548 | (defun delphi-debug-show-current-string (from to) | ||
| 1549 | (interactive "r") | ||
| 1550 | (delphi-debug-log "String: %S" (buffer-substring from to))) | ||
| 1551 | |||
| 1552 | (defun delphi-debug-show-is-stable () | ||
| 1553 | (interactive) | ||
| 1554 | (delphi-debug-log "stable: %S prev: %S next: %S" | ||
| 1555 | (delphi-is-stable-literal (point)) | ||
| 1556 | (delphi-literal-kind (1- (point))) | ||
| 1557 | (delphi-literal-kind (point)))) | ||
| 1558 | |||
| 1559 | (defun delphi-debug-unparse-buffer () | ||
| 1560 | (interactive) | ||
| 1561 | (delphi-set-text-properties (point-min) (point-max) nil)) | ||
| 1562 | |||
| 1563 | (defun delphi-debug-parse-region (from to) | ||
| 1564 | (interactive "r") | ||
| 1565 | (let ((delphi-verbose t)) | ||
| 1566 | (delphi-save-excursion | ||
| 1567 | (delphi-progress-start) | ||
| 1568 | (delphi-parse-region from to) | ||
| 1569 | (delphi-progress-done "Parsing done")))) | ||
| 1570 | |||
| 1571 | (defun delphi-debug-parse-window () | ||
| 1572 | (interactive) | ||
| 1573 | (delphi-debug-parse-region (window-start) (window-end))) | ||
| 1574 | |||
| 1575 | (defun delphi-debug-parse-buffer () | ||
| 1576 | (interactive) | ||
| 1577 | (delphi-debug-parse-region (point-min) (point-max))) | ||
| 1578 | |||
| 1579 | (defun delphi-debug-fontify-window () | ||
| 1580 | (interactive) | ||
| 1581 | (delphi-fontify-region (window-start) (window-end) t)) | ||
| 1582 | |||
| 1583 | (defun delphi-debug-fontify-buffer () | ||
| 1584 | (interactive) | ||
| 1585 | (delphi-fontify-region (point-min) (point-max) t)) | ||
| 1586 | |||
| 1587 | (defun delphi-debug-tokenize-region (from to) | ||
| 1588 | (interactive) | ||
| 1589 | (delphi-save-excursion | ||
| 1590 | (delphi-progress-start) | ||
| 1591 | (goto-char from) | ||
| 1592 | (while (< (point) to) | ||
| 1593 | (goto-char (delphi-token-end (delphi-current-token))) | ||
| 1594 | (delphi-step-progress (point) "Tokenizing" delphi-scanning-progress-step)) | ||
| 1595 | (delphi-progress-done "Tokenizing done"))) | ||
| 1596 | |||
| 1597 | (defun delphi-debug-tokenize-buffer () | ||
| 1598 | (interactive) | ||
| 1599 | (delphi-debug-tokenize-region (point-min) (point-max))) | ||
| 1600 | |||
| 1601 | (defun delphi-debug-tokenize-window () | ||
| 1602 | (interactive) | ||
| 1603 | (delphi-debug-tokenize-region (window-start) (window-end))) | ||
| 1604 | |||
| 1605 | (defun delphi-newline () | ||
| 1606 | "Terminate the current line with a newline and indent the next." | ||
| 1607 | (interactive) | ||
| 1608 | ;; Remove trailing spaces | ||
| 1609 | (delete-horizontal-space) | ||
| 1610 | (newline) | ||
| 1611 | ;; Indent both the (now) previous and current line first. | ||
| 1612 | (save-excursion | ||
| 1613 | (previous-line 1) | ||
| 1614 | (delphi-indent-line)) | ||
| 1615 | (delphi-indent-line)) | ||
| 1616 | |||
| 1617 | |||
| 1618 | (defun delphi-tab () | ||
| 1619 | "Indent the current line or insert a TAB, depending on the value of | ||
| 1620 | delphi-tab-always-indent and the current line position." | ||
| 1621 | (interactive) | ||
| 1622 | (if (or delphi-tab-always-indent ; We are always indenting | ||
| 1623 | ;; Or we are before the first non-space character on the line. | ||
| 1624 | (save-excursion (skip-chars-backward delphi-space-chars) (bolp))) | ||
| 1625 | (delphi-indent-line) | ||
| 1626 | (insert "\t"))) | ||
| 1627 | |||
| 1628 | |||
| 1629 | (defun delphi-is-directory (path) | ||
| 1630 | ;; True if the specified path is an existing directory. | ||
| 1631 | (let ((attributes (file-attributes path))) | ||
| 1632 | (and attributes (car attributes)))) | ||
| 1633 | |||
| 1634 | (defun delphi-is-file (path) | ||
| 1635 | ;; True if the specified file exists as a file. | ||
| 1636 | (let ((attributes (file-attributes path))) | ||
| 1637 | (and attributes (null (car attributes))))) | ||
| 1638 | |||
| 1639 | (defun delphi-search-directory (unit dir &optional recurse) | ||
| 1640 | ;; Searches for the unit in the specified directory. If recurse is true, then | ||
| 1641 | ;; the directory is recursively searched. File name comparison is done in a | ||
| 1642 | ;; case insensitive manner. | ||
| 1643 | (when (delphi-is-directory dir) | ||
| 1644 | (let ((files (directory-files dir)) | ||
| 1645 | (unit-file (downcase unit))) | ||
| 1646 | (catch 'done | ||
| 1647 | ;; Search for the file. | ||
| 1648 | (mapcar #'(lambda (file) | ||
| 1649 | (let ((path (concat dir "/" file))) | ||
| 1650 | (if (and (string= unit-file (downcase file)) | ||
| 1651 | (delphi-is-file path)) | ||
| 1652 | (throw 'done path)))) | ||
| 1653 | files) | ||
| 1654 | |||
| 1655 | ;; Not found. Search subdirectories. | ||
| 1656 | (when recurse | ||
| 1657 | (mapcar #'(lambda (subdir) | ||
| 1658 | (unless (member subdir '("." "..")) | ||
| 1659 | (let ((path (delphi-search-directory | ||
| 1660 | unit (concat dir "/" subdir) recurse))) | ||
| 1661 | (if path (throw 'done path))))) | ||
| 1662 | files)) | ||
| 1663 | |||
| 1664 | ;; Not found. | ||
| 1665 | nil)))) | ||
| 1666 | |||
| 1667 | |||
| 1668 | (defun delphi-find-unit-in-directory (unit dir) | ||
| 1669 | ;; Searches for the unit in the specified directory. If the directory ends | ||
| 1670 | ;; in \"...\", then it is recursively searched. | ||
| 1671 | (let ((dir-name dir) | ||
| 1672 | (recurse nil)) | ||
| 1673 | ;; Check if we need to recursively search the directory. | ||
| 1674 | (if (string-match "^\\(.+\\)\\.\\.\\.$" dir-name) | ||
| 1675 | (setq dir-name (match-string 1 dir-name) | ||
| 1676 | recurse t)) | ||
| 1677 | ;; Ensure the trailing slash is removed. | ||
| 1678 | (if (string-match "^\\(.+\\)[\\\\/]$" dir-name) | ||
| 1679 | (setq dir-name (match-string 1 dir-name))) | ||
| 1680 | (delphi-search-directory unit dir-name recurse))) | ||
| 1681 | |||
| 1682 | (defun delphi-find-unit-file (unit) | ||
| 1683 | ;; Finds the specified delphi source file according to `delphi-search-path'. | ||
| 1684 | ;; If found, the full path is returned, otherwise nil is returned. | ||
| 1685 | (catch 'done | ||
| 1686 | (cond ((null delphi-search-path) | ||
| 1687 | (delphi-find-unit-in-directory unit ".")) | ||
| 1688 | |||
| 1689 | ((stringp delphi-search-path) | ||
| 1690 | (delphi-find-unit-in-directory unit delphi-search-path)) | ||
| 1691 | |||
| 1692 | ((mapcar | ||
| 1693 | #'(lambda (dir) | ||
| 1694 | (let ((file (delphi-find-unit-in-directory unit dir))) | ||
| 1695 | (if file (throw 'done file)))) | ||
| 1696 | delphi-search-path))) | ||
| 1697 | nil)) | ||
| 1698 | |||
| 1699 | (defun delphi-find-unit (unit) | ||
| 1700 | "Finds the specified delphi source file according to `delphi-search-path'. | ||
| 1701 | If no extension is specified, .pas is assumed. Creates a buffer for the unit." | ||
| 1702 | (interactive "sDelphi unit name: ") | ||
| 1703 | (let* ((unit-file (if (string-match "^\\(.*\\)\\.[a-z]+$" unit) | ||
| 1704 | unit | ||
| 1705 | (concat unit ".pas"))) | ||
| 1706 | (file (delphi-find-unit-file unit-file))) | ||
| 1707 | (if (null file) | ||
| 1708 | (error "unit not found: %s" unit-file) | ||
| 1709 | (find-file file) | ||
| 1710 | (if (not (eq major-mode 'delphi-mode)) | ||
| 1711 | (delphi-mode))) | ||
| 1712 | file)) | ||
| 1713 | |||
| 1714 | (defun delphi-find-current-def () | ||
| 1715 | "Find the definition of the identifier under the current point." | ||
| 1716 | (interactive) | ||
| 1717 | (error "delphi-find-current-def: not implemented yet")) | ||
| 1718 | |||
| 1719 | (defun delphi-find-current-xdef () | ||
| 1720 | "Find the definition of the identifier under the current point, searching | ||
| 1721 | in external units if necessary (as listed in the current unit's use clause). | ||
| 1722 | The set of directories to search for a unit is specified by the global variable | ||
| 1723 | delphi-search-path." | ||
| 1724 | (interactive) | ||
| 1725 | (error "delphi-find-current-xdef: not implemented yet")) | ||
| 1726 | |||
| 1727 | (defun delphi-find-current-body () | ||
| 1728 | "Find the body of the identifier under the current point, assuming | ||
| 1729 | it is a routine." | ||
| 1730 | (interactive) | ||
| 1731 | (error "delphi-find-current-body: not implemented yet")) | ||
| 1732 | |||
| 1733 | (defun delphi-fill-comment () | ||
| 1734 | "Fills the text of the current comment, according to `fill-column'. | ||
| 1735 | An error is raised if not in a comment." | ||
| 1736 | (interactive) | ||
| 1737 | (save-excursion | ||
| 1738 | (let* ((comment (delphi-current-token)) | ||
| 1739 | (comment-kind (delphi-token-kind comment))) | ||
| 1740 | (if (not (delphi-is comment-kind delphi-comments)) | ||
| 1741 | (error "Not in a comment") | ||
| 1742 | (let* ((start-comment (delphi-comment-block-start comment)) | ||
| 1743 | (end-comment (delphi-comment-block-end comment)) | ||
| 1744 | (comment-start (delphi-token-start start-comment)) | ||
| 1745 | (comment-end (delphi-token-end end-comment)) | ||
| 1746 | (content-start (delphi-comment-content-start start-comment)) | ||
| 1747 | (content-indent (delphi-column-of content-start)) | ||
| 1748 | (content-prefix (make-string content-indent ?\ )) | ||
| 1749 | (content-prefix-re delphi-leading-spaces-re) | ||
| 1750 | (p nil) | ||
| 1751 | (marked-point (point-marker))) ; Maintain our position reliably. | ||
| 1752 | (when (eq 'comment-single-line comment-kind) | ||
| 1753 | ;; // style comments need more work. | ||
| 1754 | (setq content-prefix | ||
| 1755 | (let ((comment-indent (delphi-column-of comment-start))) | ||
| 1756 | (concat (make-string comment-indent ?\ ) "//" | ||
| 1757 | (make-string (- content-indent comment-indent 2) | ||
| 1758 | ?\ ))) | ||
| 1759 | content-prefix-re (concat delphi-leading-spaces-re | ||
| 1760 | "//" | ||
| 1761 | delphi-spaces-re) | ||
| 1762 | comment-end (if (delphi-is-literal-end comment-end) | ||
| 1763 | ;; Don't include the trailing newline. | ||
| 1764 | (1- comment-end) | ||
| 1765 | comment-end))) | ||
| 1766 | |||
| 1767 | ;; Advance our marked point after inserted spaces. | ||
| 1768 | (set-marker-insertion-type marked-point t) | ||
| 1769 | |||
| 1770 | ;; Ensure we can modify the buffer | ||
| 1771 | (goto-char content-start) | ||
| 1772 | (insert " ") | ||
| 1773 | (delete-char -1) | ||
| 1774 | |||
| 1775 | (narrow-to-region content-start comment-end) | ||
| 1776 | |||
| 1777 | ;; Strip off the comment prefixes | ||
| 1778 | (setq p (point-min)) | ||
| 1779 | (while (when (< p (point-max)) | ||
| 1780 | (goto-char p) | ||
| 1781 | (re-search-forward content-prefix-re nil t)) | ||
| 1782 | (replace-match "" nil nil) | ||
| 1783 | (setq p (1+ (point)))) | ||
| 1784 | |||
| 1785 | ;; add an extra line to prevent the fill from doing it for us. | ||
| 1786 | (goto-char (point-max)) | ||
| 1787 | (insert "\n") | ||
| 1788 | |||
| 1789 | ;; Fill the comment contents. | ||
| 1790 | (let ((fill-column (- fill-column content-indent))) | ||
| 1791 | (fill-region (point-min) (point-max))) | ||
| 1792 | |||
| 1793 | (goto-char (point-max)) | ||
| 1794 | (delete-char -1) | ||
| 1795 | |||
| 1796 | ;; Restore comment prefixes. | ||
| 1797 | (goto-char (point-min)) | ||
| 1798 | (end-of-line) ; Don't reset the first line. | ||
| 1799 | (setq p (point)) | ||
| 1800 | (while (when (< p (point-max)) | ||
| 1801 | (goto-char p) | ||
| 1802 | (re-search-forward "^" nil t)) | ||
| 1803 | (replace-match content-prefix nil nil) | ||
| 1804 | (setq p (1+ (point)))) | ||
| 1805 | |||
| 1806 | (setq comment-end (point-max)) | ||
| 1807 | (widen) | ||
| 1808 | |||
| 1809 | ;; Restore our position | ||
| 1810 | (goto-char marked-point) | ||
| 1811 | (set-marker marked-point nil) | ||
| 1812 | |||
| 1813 | ;; React to the entire fill change as a whole. | ||
| 1814 | (delphi-progress-start) | ||
| 1815 | (delphi-parse-region comment-start comment-end) | ||
| 1816 | (delphi-progress-done)))))) | ||
| 1817 | |||
| 1818 | (defun delphi-new-comment-line () | ||
| 1819 | "If in a // comment, does a newline, indented such that one is still in the | ||
| 1820 | comment block. If not in a // comment, just does a normal newline." | ||
| 1821 | (interactive) | ||
| 1822 | (let ((comment (delphi-current-token))) | ||
| 1823 | (if (not (eq 'comment-single-line (delphi-token-kind comment))) | ||
| 1824 | ;; Not in a // comment. Just do the normal newline. | ||
| 1825 | (delphi-newline) | ||
| 1826 | (let* ((start-comment (delphi-comment-block-start comment)) | ||
| 1827 | (comment-start (delphi-token-start start-comment)) | ||
| 1828 | (content-start (delphi-comment-content-start start-comment)) | ||
| 1829 | (prefix | ||
| 1830 | (concat (make-string (delphi-column-of comment-start) ?\ ) "//" | ||
| 1831 | (make-string (- content-start comment-start 2) ?\ )))) | ||
| 1832 | (delete-horizontal-space) | ||
| 1833 | (newline) | ||
| 1834 | (insert prefix))))) | ||
| 1835 | |||
| 1836 | (defun delphi-match-token (token limit) | ||
| 1837 | ;; Sets the match region used by (match-string 0) and friends to the token's | ||
| 1838 | ;; region. Sets the current point to the end of the token (or limit). | ||
| 1839 | (set-match-data nil) | ||
| 1840 | (if token | ||
| 1841 | (let ((end (min (delphi-token-end token) limit))) | ||
| 1842 | (set-match-data (list (delphi-token-start token) end)) | ||
| 1843 | (goto-char end) | ||
| 1844 | token))) | ||
| 1845 | |||
| 1846 | (defconst delphi-font-lock-defaults | ||
| 1847 | '(nil ; We have our own fontify routine, so keywords don't apply. | ||
| 1848 | t ; Syntactic fontification doesn't apply. | ||
| 1849 | nil ; Don't care about case since we don't use regexps to find tokens. | ||
| 1850 | nil ; Syntax alists don't apply. | ||
| 1851 | nil ; Syntax begin movement doesn't apply | ||
| 1852 | (font-lock-fontify-region-function . delphi-fontify-region) | ||
| 1853 | (font-lock-verbose . delphi-fontifying-progress-step)) | ||
| 1854 | "Delphi mode font-lock defaults. Syntactic fontification is ignored.") | ||
| 1855 | |||
| 1856 | (defvar delphi-debug-mode-map | ||
| 1857 | (let ((kmap (make-sparse-keymap))) | ||
| 1858 | (mapcar #'(lambda (binding) (define-key kmap (car binding) (cadr binding))) | ||
| 1859 | '(("n" delphi-debug-goto-next-token) | ||
| 1860 | ("p" delphi-debug-goto-previous-token) | ||
| 1861 | ("t" delphi-debug-show-current-token) | ||
| 1862 | ("T" delphi-debug-tokenize-buffer) | ||
| 1863 | ("W" delphi-debug-tokenize-window) | ||
| 1864 | ("g" delphi-debug-goto-point) | ||
| 1865 | ("s" delphi-debug-show-current-string) | ||
| 1866 | ("a" delphi-debug-parse-buffer) | ||
| 1867 | ("w" delphi-debug-parse-window) | ||
| 1868 | ("f" delphi-debug-fontify-window) | ||
| 1869 | ("F" delphi-debug-fontify-buffer) | ||
| 1870 | ("r" delphi-debug-parse-region) | ||
| 1871 | ("c" delphi-debug-unparse-buffer) | ||
| 1872 | ("x" delphi-debug-show-is-stable) | ||
| 1873 | )) | ||
| 1874 | kmap) | ||
| 1875 | "Keystrokes for delphi-mode debug commands.") | ||
| 1876 | |||
| 1877 | (defvar delphi-mode-map | ||
| 1878 | (let ((kmap (make-sparse-keymap))) | ||
| 1879 | (mapcar #'(lambda (binding) (define-key kmap (car binding) (cadr binding))) | ||
| 1880 | (list '("\r" delphi-newline) | ||
| 1881 | '("\t" delphi-tab) | ||
| 1882 | '("\177" backward-delete-char-untabify) | ||
| 1883 | '("\C-cd" delphi-find-current-def) | ||
| 1884 | '("\C-cx" delphi-find-current-xdef) | ||
| 1885 | '("\C-cb" delphi-find-current-body) | ||
| 1886 | '("\C-cu" delphi-find-unit) | ||
| 1887 | '("\M-q" delphi-fill-comment) | ||
| 1888 | '("\M-j" delphi-new-comment-line) | ||
| 1889 | ;; Debug bindings: | ||
| 1890 | (list "\C-c\C-d" delphi-debug-mode-map))) | ||
| 1891 | kmap) | ||
| 1892 | "Keymap used in Delphi mode.") | ||
| 1893 | |||
| 1894 | (defconst delphi-mode-syntax-table (make-syntax-table) | ||
| 1895 | "Delphi mode's syntax table. It is just a standard syntax table. | ||
| 1896 | This is ok since we do our own keyword/comment/string face coloring.") | ||
| 1897 | |||
| 1898 | ;;;###autoload | ||
| 1899 | (defun delphi-mode (&optional skip-initial-parsing) | ||
| 1900 | "Major mode for editing Delphi code. \\<delphi-mode-map> | ||
| 1901 | \\[delphi-tab]\t- Indents the current line for Delphi code. | ||
| 1902 | \\[delphi-find-current-def]\t- Find previous definition of identifier at the point. | ||
| 1903 | \\[delphi-find-current-xdef]\t- Find definition, but also in external units. | ||
| 1904 | \\[delphi-find-current-body]\t- Find the body of the identifier at the point. | ||
| 1905 | \\[delphi-find-unit]\t- Search for a Delphi source file. | ||
| 1906 | \\[delphi-fill-comment]\t- Fill the current comment. | ||
| 1907 | \\[delphi-new-comment-line]\t- If in a // comment, do a new comment line. | ||
| 1908 | |||
| 1909 | M-x indent-region also works for indenting a whole region. | ||
| 1910 | |||
| 1911 | Customization: | ||
| 1912 | |||
| 1913 | `delphi-indent-level' (default 3) | ||
| 1914 | Indentation of Delphi statements with respect to containing block. | ||
| 1915 | `delphi-compound-block-indent' (default 0) | ||
| 1916 | Extra indentation for blocks in compound statements. | ||
| 1917 | `delphi-case-label-indent' (default 0) | ||
| 1918 | Extra indentation for case statement labels. | ||
| 1919 | `delphi-tab-always-indent' (default t) | ||
| 1920 | Non-nil means TAB in Delphi mode should always reindent the current line, | ||
| 1921 | regardless of where in the line point is when the TAB command is used. | ||
| 1922 | `delphi-search-path' (default .) | ||
| 1923 | Directories to search when finding external units. | ||
| 1924 | `delphi-verbose' (default nil) | ||
| 1925 | If true then delphi token processing progress is reported to the user. | ||
| 1926 | |||
| 1927 | Coloring: | ||
| 1928 | |||
| 1929 | `delphi-comment-face' (default font-lock-comment-face) | ||
| 1930 | Face used to color delphi comments. | ||
| 1931 | `delphi-string-face' (default font-lock-string-face) | ||
| 1932 | Face used to color delphi strings. | ||
| 1933 | `delphi-keyword-face' (default font-lock-keyword-face) | ||
| 1934 | Face used to color delphi keywords. | ||
| 1935 | `delphi-other-face' (default nil) | ||
| 1936 | Face used to color everything else. | ||
| 1937 | |||
| 1938 | Turning on Delphi mode calls the value of the variable delphi-mode-hook with | ||
| 1939 | no args, if that value is non-nil." | ||
| 1940 | (interactive) | ||
| 1941 | (kill-all-local-variables) | ||
| 1942 | (use-local-map delphi-mode-map) | ||
| 1943 | (setq major-mode 'delphi-mode) | ||
| 1944 | (setq mode-name "Delphi") | ||
| 1945 | |||
| 1946 | (setq local-abbrev-table delphi-mode-abbrev-table) | ||
| 1947 | (set-syntax-table delphi-mode-syntax-table) | ||
| 1948 | |||
| 1949 | ;; Buffer locals: | ||
| 1950 | (mapcar #'(lambda (var) | ||
| 1951 | (let ((var-symb (car var)) | ||
| 1952 | (var-val (cadr var))) | ||
| 1953 | (make-local-variable var-symb) | ||
| 1954 | (set var-symb var-val))) | ||
| 1955 | (list '(indent-line-function delphi-indent-line) | ||
| 1956 | '(comment-indent-function delphi-indent-line) | ||
| 1957 | '(case-fold-search t) | ||
| 1958 | '(delphi-progress-last-reported-point nil) | ||
| 1959 | '(delphi-ignore-changes nil) | ||
| 1960 | (list 'font-lock-defaults delphi-font-lock-defaults))) | ||
| 1961 | |||
| 1962 | ;; We need to keep track of changes to the buffer to determine if we need | ||
| 1963 | ;; to retokenize changed text. | ||
| 1964 | (make-local-hook 'after-change-functions) | ||
| 1965 | (add-hook 'after-change-functions 'delphi-after-change nil t) | ||
| 1966 | |||
| 1967 | (widen) | ||
| 1968 | (unless skip-initial-parsing | ||
| 1969 | (delphi-save-excursion | ||
| 1970 | (let ((delphi-verbose t)) | ||
| 1971 | (delphi-progress-start) | ||
| 1972 | (delphi-parse-region (point-min) (point-max)) | ||
| 1973 | (delphi-progress-done)))) | ||
| 1974 | |||
| 1975 | (run-hooks 'delphi-mode-hook)) | ||