flymake.el 80.7 KB
Newer Older
Eli Zaretskii's avatar
Eli Zaretskii committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
;;; flymake.el -- a universal on-the-fly syntax checker

;; Copyright (C) 2003 Free Software Foundation

;; Author:  Pavel Kobiakov <pk_at_work@yahoo.com>
;; Maintainer: Pavel Kobiakov <pk_at_work@yahoo.com>
;; Version: 0.3
;; Keywords: c languages tools

;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:
;;
;; Flymake is a minor Emacs mode performing on-the-fly syntax
;; checks using the external syntax check tool (for C/C++ this
;; is usually the compiler)

;;; Code:

;;;; [[ Overlay compatibility
(autoload 'make-overlay            "overlay" "Overlay compatibility kit." t)
(autoload 'overlayp                "overlay" "Overlay compatibility kit." t)
(autoload 'overlays-in             "overlay" "Overlay compatibility kit." t)
(autoload 'delete-overlay          "overlay" "Overlay compatibility kit." t)
(autoload 'overlay-put             "overlay" "Overlay compatibility kit." t)
(autoload 'overlay-get             "overlay" "Overlay compatibility kit." t)
;;;; ]]

;;;; [[ cross-emacs compatibility routines
(defvar flymake-emacs
    (cond
	((string-match "XEmacs" emacs-version)  'xemacs)
	(t                                      'emacs)
	)
    "Currently used emacs flavor"
)

(defun flymake-makehash(&optional test)
    (cond
	((equal flymake-emacs 'xemacs)  (if test (make-hash-table :test test) (make-hash-table)))
56
	(t                              (makehash test))	
Eli Zaretskii's avatar
Eli Zaretskii committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    )
)

(defun flymake-time-to-float(&optional tm)
	"Convert `current-time` to a float number of seconds."
	(multiple-value-bind (s0 s1 s2) (or tm (current-time))
	  (+ (* (float (ash 1 16)) s0) (float s1) (* 0.0000001 s2)))
)
(defun flymake-float-time()
    (cond
	((equal flymake-emacs 'xemacs)  (flymake-time-to-float (current-time)))
	(t                              (float-time))
    )
)

(defun flymake-replace-regexp-in-string(regexp rep str)
    (cond
	((equal flymake-emacs 'xemacs)  (replace-in-string str regexp rep))
	(t                              (replace-regexp-in-string regexp rep str))
	)
)

(defun flymake-split-string-remove-empty-edges(str pattern)
    "split, then remove first and/or last in case it's empty"
	(let* ((splitted (split-string str pattern)))
	    (if (and (> (length splitted) 0) (= 0 (length (elt splitted 0))))
			(setq splitted (cdr splitted))
		)
	    (if (and (> (length splitted) 0) (= 0 (length (elt splitted (1- (length splitted))))))
			(setq splitted (reverse (cdr (reverse splitted))))
		)
		splitted
    )
)
91

Eli Zaretskii's avatar
Eli Zaretskii committed
92 93 94
(defun flymake-split-string(str pattern)
    (cond
	((equal flymake-emacs 'xemacs)  (flymake-split-string-remove-empty-edges str pattern))
95
	(t                              (flymake-split-string-remove-empty-edges str pattern))
Eli Zaretskii's avatar
Eli Zaretskii committed
96 97 98 99 100 101
    )
)

(defun flymake-get-temp-dir()
    (cond
	((equal flymake-emacs 'xemacs)  (temp-directory))
102
	(t                              temporary-file-directory)))
Eli Zaretskii's avatar
Eli Zaretskii committed
103

104
(defun flymake-line-beginning-position ()
Eli Zaretskii's avatar
Eli Zaretskii committed
105 106
    (save-excursion
	(beginning-of-line)
107
	(point)))
Eli Zaretskii's avatar
Eli Zaretskii committed
108

109
(defun flymake-line-end-position ()
Eli Zaretskii's avatar
Eli Zaretskii committed
110 111
    (save-excursion
	(end-of-line)
112
	(point)))
Eli Zaretskii's avatar
Eli Zaretskii committed
113 114 115 116 117 118 119 120 121 122 123 124

(defun flymake-popup-menu(pos menu-data)
    (cond
       ((equal flymake-emacs 'xemacs)
	    (let* ((x-pos       (nth 0 (nth 0 pos)))
		   (y-pos       (nth 1 (nth 0 pos)))
		   (fake-event-props  '(button 1 x 1 y 1)))
		(setq fake-event-props (plist-put fake-event-props 'x x-pos))
		(setq fake-event-props (plist-put fake-event-props 'y y-pos))
		(popup-menu (flymake-make-xemacs-menu menu-data) (make-event 'button-press fake-event-props))
	    )
       )
125
       (t (x-popup-menu pos (flymake-make-emacs-menu menu-data)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
126 127 128 129 130 131 132 133 134

(defun flymake-make-emacs-menu(menu-data)
    (let* ((menu-title     (nth 0 menu-data))
		   (menu-items     (nth 1 menu-data))
		   (menu-commands  nil))

	(setq menu-commands (mapcar (lambda (foo)
				      (cons (nth 0 foo) (nth 1 foo)))
				    menu-items))
135
		(list menu-title (cons "" menu-commands))))
Eli Zaretskii's avatar
Eli Zaretskii committed
136

137
(defun flymake-nop ())
Eli Zaretskii's avatar
Eli Zaretskii committed
138

139
(defun flymake-make-xemacs-menu (menu-data)
Eli Zaretskii's avatar
Eli Zaretskii committed
140 141 142 143 144 145
    (let* ((menu-title     (nth 0 menu-data))
	   (menu-items     (nth 1 menu-data))
	   (menu-commands  nil))
	(setq menu-commands (mapcar (lambda (foo)
				      (vector (nth 0 foo) (or (nth 1 foo) '(flymake-nop)) t))
				    menu-items))
146
	(cons menu-title menu-commands)))
Eli Zaretskii's avatar
Eli Zaretskii committed
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183

(defun flymake-xemacs-window-edges(&optional window)
    (let ((edges  (window-pixel-edges window))
	  tmp)
	(setq tmp edges)
	(setcar tmp (/ (car tmp) (face-width 'default)))
	(setq tmp (cdr tmp))
	(setcar tmp (/ (car tmp) (face-height 'default)))
	(setq tmp (cdr tmp))
	(setcar tmp (/ (car tmp) (face-width 'default)))
	(setq tmp (cdr tmp))
	(setcar tmp (/ (car tmp) (face-height 'default)))
	edges
    )
)

(defun flymake-current-row()
    "return current row in current frame"
    (cond
       ((equal flymake-emacs 'xemacs)  (count-lines (window-start) (point)))
       (t                              (+ (car (cdr (window-edges))) (count-lines (window-start) (point))))
    )
)

(defun flymake-selected-frame()
    (cond
       ((equal flymake-emacs 'xemacs)  (selected-window))
       (t                              (selected-frame))
    )
)

;;;; ]]

(defcustom flymake-log-level -1
    "Logging level, only messages with level > flymake-log-level will not be logged
-1 = NONE, 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG"
    :group 'flymake
184
    :type 'integer)
Eli Zaretskii's avatar
Eli Zaretskii committed
185

186 187
(defun flymake-log (level text &rest args)
    "Log a message with optional arguments."
Eli Zaretskii's avatar
Eli Zaretskii committed
188 189 190 191 192 193 194 195
    (if (<= level flymake-log-level)
	(let* ((msg (apply 'format text args)))
	    (message msg)
	    ;(with-temp-buffer
	    ;    (insert msg)
	    ;   (insert "\n")
	    ;   (flymake-save-buffer-in-file (current-buffer) "d:/flymake.log" t)  ; make log file name customizable
	    ;)
196
	)))
Eli Zaretskii's avatar
Eli Zaretskii committed
197

198 199
(defun flymake-ins-after (list pos val)
    "Insert VAL into LIST after position POS."
Eli Zaretskii's avatar
Eli Zaretskii committed
200 201
    (let ((tmp (copy-sequence list))) ; (???)
	(setcdr (nthcdr pos tmp) (cons val (nthcdr (1+ pos) tmp)))
202
	tmp))
Eli Zaretskii's avatar
Eli Zaretskii committed
203

204 205
(defun flymake-set-at (list pos val)
    "Set VAL at position POS in LIST"
Eli Zaretskii's avatar
Eli Zaretskii committed
206 207
    (let ((tmp (copy-sequence list))) ; (???)
	(setcar (nthcdr pos tmp) val)
208
	tmp))
Eli Zaretskii's avatar
Eli Zaretskii committed
209

210 211
(defvar flymake-pid-to-names (flymake-makehash)
  "pid -> source buffer name, output file name mapping.")
Eli Zaretskii's avatar
Eli Zaretskii committed
212

213 214
(defun flymake-reg-names (pid source-buffer-name)
    "Save into in PID map."
Eli Zaretskii's avatar
Eli Zaretskii committed
215
    (unless (stringp source-buffer-name)
216 217
	(error "Invalid buffer name"))
    (puthash pid (list source-buffer-name) flymake-pid-to-names))
Eli Zaretskii's avatar
Eli Zaretskii committed
218

219 220 221
(defun flymake-get-source-buffer-name (pid)
    "Return buffer name stored in PID map."
    (nth 0 (gethash pid flymake-pid-to-names)))
Eli Zaretskii's avatar
Eli Zaretskii committed
222

223 224 225
(defun flymake-unreg-names (pid)
    "Delete PID->buffer name mapping."
    (remhash pid flymake-pid-to-names))
Eli Zaretskii's avatar
Eli Zaretskii committed
226

227 228
(defun flymake-get-buffer-var (buffer var-name)
    "Switch to BUFFER if necessary and return local variable VAR-NAME."
Eli Zaretskii's avatar
Eli Zaretskii committed
229
    (unless (bufferp buffer)
230
	(error "Invalid buffer"))
Eli Zaretskii's avatar
Eli Zaretskii committed
231 232 233 234 235

    (if (eq buffer (current-buffer))
	(symbol-value var-name)
	(save-excursion
	    (set-buffer buffer)
236
	    (symbol-value var-name))))
Eli Zaretskii's avatar
Eli Zaretskii committed
237

238 239
(defun flymake-set-buffer-var (buffer var-name var-value)
    "Switch to BUFFER if necessary and set local variable VAR-NAME to VAR-VALUE."
Eli Zaretskii's avatar
Eli Zaretskii committed
240
    (unless (bufferp buffer)
241
	(error "Invalid buffer"))
Eli Zaretskii's avatar
Eli Zaretskii committed
242 243 244 245 246

    (if (eq buffer (current-buffer))
	(set var-name var-value)
	(save-excursion
	    (set-buffer buffer)
247 248 249 250
	    (set var-name var-value))))

(defvar flymake-buffer-data (flymake-makehash)
  "Data specific to syntax check tool, in name-value pairs.")
Eli Zaretskii's avatar
Eli Zaretskii committed
251 252 253

(make-variable-buffer-local 'flymake-buffer-data)

254 255 256 257 258 259 260 261 262 263 264 265 266 267
(defun flymake-get-buffer-data (buffer)
    (flymake-get-buffer-var buffer 'flymake-buffer-data))

(defun flymake-set-buffer-data (buffer data)
    (flymake-set-buffer-var buffer 'flymake-buffer-data data))

(defun flymake-get-buffer-value (buffer name)
    (gethash name (flymake-get-buffer-data buffer)))

(defun flymake-set-buffer-value (buffer name value)
    (puthash name value (flymake-get-buffer-data buffer)))

(defvar flymake-output-residual nil "")

Eli Zaretskii's avatar
Eli Zaretskii committed
268
(make-variable-buffer-local 'flymake-output-residual)
269 270 271 272 273 274

(defun flymake-get-buffer-output-residual (buffer)
    (flymake-get-buffer-var buffer 'flymake-output-residual))

(defun flymake-set-buffer-output-residual (buffer residual)
    (flymake-set-buffer-var buffer 'flymake-output-residual residual))
Eli Zaretskii's avatar
Eli Zaretskii committed
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

(defcustom flymake-allowed-file-name-masks '((".+\\.c$" flymake-simple-make-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.cpp$" flymake-simple-make-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.xml$" flymake-xml-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.html?$" flymake-xml-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.cs$" flymake-simple-make-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.pl$" flymake-perl-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.h$" flymake-master-make-header-init flymake-master-cleanup flymake-get-real-file-name)
					     (".+\\.java$" flymake-simple-make-java-init flymake-simple-java-cleanup flymake-get-real-file-name)
					     (".+[0-9]+\\.tex$" flymake-master-tex-init flymake-master-cleanup flymake-get-real-file-name)
					     (".+\\.tex$" flymake-simple-tex-init flymake-simple-cleanup flymake-get-real-file-name)
					     (".+\\.idl$" flymake-simple-make-init flymake-simple-cleanup flymake-get-real-file-name)
;                                            (".+\\.cpp$" 1)
;                                            (".+\\.java$" 3)
;                                            (".+\\.h$" 2 (".+\\.cpp$" ".+\\.c$")
;                                                ("[ \t]*#[ \t]*include[ \t]*\"\\([\w0-9/\\_\.]*[/\\]*\\)\\(%s\\)\"" 1 2))
;                                            (".+\\.idl$" 1)
;                                            (".+\\.odl$" 1)
;                                            (".+[0-9]+\\.tex$" 2 (".+\\.tex$")
;                                                ("[ \t]*\\input[ \t]*{\\(.*\\)\\(%s\\)}" 1 2 ))
;                                            (".+\\.tex$" 1)
					     )
297
    "*Files syntax checking is allowed for."
Eli Zaretskii's avatar
Eli Zaretskii committed
298
    :group 'flymake
299
    :type '(repeat (string symbol symbol symbol)))
Eli Zaretskii's avatar
Eli Zaretskii committed
300

301 302
(defun flymake-get-file-name-mode-and-masks (file-name)
    "Return the corresponding entry from 'flymake-allowed-file-name-masks'."
Eli Zaretskii's avatar
Eli Zaretskii committed
303
    (unless (stringp file-name)
304
	(error "Invalid file-name"))
Eli Zaretskii's avatar
Eli Zaretskii committed
305 306 307 308 309
    (let ((count           (length flymake-allowed-file-name-masks))
	  (idx             0)
	  (mode-and-masks  nil))
	(while (and (not mode-and-masks) (< idx count))
	    (if (string-match (nth 0 (nth idx flymake-allowed-file-name-masks)) file-name)
310 311
		(setq mode-and-masks (cdr (nth idx flymake-allowed-file-name-masks))))
	    (setq idx (1+ idx)))
Eli Zaretskii's avatar
Eli Zaretskii committed
312
	(flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks))
313
	mode-and-masks))
Eli Zaretskii's avatar
Eli Zaretskii committed
314

315 316 317 318
(defun flymake-can-syntax-check-file (file-name)
    "Determine whether we can syntax check FILE-NAME.
Return nil if we cannot, non-nil if we can."
    (if (flymake-get-init-function file-name) t nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
319

320 321
(defun flymake-get-init-function (file-name)
    "Return init function to be used for the file."
Eli Zaretskii's avatar
Eli Zaretskii committed
322 323 324
    (let* ((init-f  (nth 0 (flymake-get-file-name-mode-and-masks file-name))))
	;(flymake-log 0 "calling %s" init-f)
	;(funcall init-f (current-buffer))
325
	  init-f))
Eli Zaretskii's avatar
Eli Zaretskii committed
326

327 328 329
(defun flymake-get-cleanup-function (file-name)
    "Return cleanup function to be used for the file."
    (nth 1 (flymake-get-file-name-mode-and-masks file-name)))
Eli Zaretskii's avatar
Eli Zaretskii committed
330

331 332
(defun flymake-get-real-file-name-function (file-name)
    (or (nth 2 (flymake-get-file-name-mode-and-masks file-name)) 'flymake-get-real-file-name))
Eli Zaretskii's avatar
Eli Zaretskii committed
333 334

(defcustom flymake-buildfile-dirs '("." ".." "../.." "../../.." "../../../.." "../../../../.." "../../../../../.." "../../../../../../.." "../../../../../../../.." "../../../../../../../../.." "../../../../../../../../../.." "../../../../../../../../../../..")
335
    "Dirs to look for buildfile."
Eli Zaretskii's avatar
Eli Zaretskii committed
336
    :group 'flymake
337
    :type '(repeat (string)))
Eli Zaretskii's avatar
Eli Zaretskii committed
338 339 340

(defvar flymake-find-buildfile-cache (flymake-makehash 'equal))

341 342 343 344 345 346 347 348 349 350 351 352 353
(defun flymake-get-buildfile-from-cache (dir-name)
    (gethash dir-name flymake-find-buildfile-cache))

(defun flymake-add-buildfile-to-cache (dir-name buildfile)
    (puthash dir-name buildfile flymake-find-buildfile-cache))

(defun flymake-clear-buildfile-cache ()
    (clrhash flymake-find-buildfile-cache))

(defun flymake-find-buildfile (buildfile-name source-dir-name dirs)
    "Find buildfile starting from current directory.
Buildfile includes Makefile, build.xml etc.
Return its path if found, or nil if not found."
Eli Zaretskii's avatar
Eli Zaretskii committed
354 355
    (if (flymake-get-buildfile-from-cache source-dir-name)
	(progn
356
	    (flymake-get-buildfile-from-cache source-dir-name))
Eli Zaretskii's avatar
Eli Zaretskii committed
357 358 359 360 361 362 363 364 365
	(let* ((buildfile-dir          nil)
	       (buildfile              nil)
	       (dir-count              (length dirs))
	       (dir-idx                0)
	       (found                  nil))
	    (while (and (not found) (< dir-idx dir-count))
		(setq buildfile-dir (concat source-dir-name (nth dir-idx dirs)))
		(setq buildfile (concat buildfile-dir "/" buildfile-name))
		(when (file-exists-p buildfile)
366 367
		    (setq found t))
		(setq dir-idx (1+ dir-idx)))
Eli Zaretskii's avatar
Eli Zaretskii committed
368 369 370 371
	    (if found
		(progn
		    (flymake-log 3 "found buildfile at %s/%s" buildfile-dir buildfile-name)
		    (flymake-add-buildfile-to-cache source-dir-name buildfile-dir)
372
		    buildfile-dir)
Eli Zaretskii's avatar
Eli Zaretskii committed
373 374
		(progn
		    (flymake-log 3 "buildfile for %s not found" source-dir-name)
375
		    nil)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
376

377 378
(defun flymake-fix-path-name (name)
    "Replace all occurences of '\' with '/'."
Eli Zaretskii's avatar
Eli Zaretskii committed
379 380 381 382 383
    (when name
	(let* ((new-name (flymake-replace-regexp-in-string "[\\]" "/" (expand-file-name name)))
	       (last-char (elt new-name (1- (length new-name)))))
	    (setq new-name (flymake-replace-regexp-in-string "\\./" "" new-name))
	    (if (equal "/" (char-to-string last-char))
384 385
		(setq new-name (substring new-name 0 (1- (length new-name)))))
	    new-name)))
Eli Zaretskii's avatar
Eli Zaretskii committed
386

387 388 389 390
(defun flymake-same-files (file-name-one file-name-two)
    "Check if FILE-NAME-ONE and FILE-NAME-TWO point to same file.
Return t if so, nil if not."
    (equal (flymake-fix-path-name file-name-one) (flymake-fix-path-name file-name-two)))
Eli Zaretskii's avatar
Eli Zaretskii committed
391

392
(defun flymake-ensure-ends-with-slash (path)
Eli Zaretskii's avatar
Eli Zaretskii committed
393 394
    (if (not (= (elt path (1- (length path))) (string-to-char "/")))
	(concat path "/")
395
	path))
Eli Zaretskii's avatar
Eli Zaretskii committed
396

397 398
(defun flymake-get-common-path-prefix (string-one string-two)
    "Return common prefix for two paths STRING-ONE and STRING-TWO."
Eli Zaretskii's avatar
Eli Zaretskii committed
399 400 401 402 403 404 405 406 407 408 409 410 411 412
    (when (and string-one string-two)
	(let* ((slash-pos-one  -1)
	       (slash-pos-two  -1)
	       (done           nil)
	       (prefix         nil))
	    (setq string-one (flymake-ensure-ends-with-slash string-one))
	    (setq string-two (flymake-ensure-ends-with-slash string-two))
	    (while (not done)
		(setq slash-pos-one (string-match "/" string-one (1+ slash-pos-one)))
		(setq slash-pos-two (string-match "/" string-two (1+ slash-pos-two)))
		(if (and slash-pos-one slash-pos-two
			 (= slash-pos-one slash-pos-two)
			 (string= (substring string-one 0 slash-pos-one) (substring string-two 0 slash-pos-two)))
		    (progn
413 414 415
			(setq prefix (substring string-one 0 (1+ slash-pos-one))))
		    (setq done t)))
	    prefix)))
Eli Zaretskii's avatar
Eli Zaretskii committed
416

417 418
(defun flymake-build-relative-path (from-dir to-dir)
    "Return rel: FROM-DIR/rel == TO-DIR."
Eli Zaretskii's avatar
Eli Zaretskii committed
419
    (if (not (equal (elt from-dir 0) (elt to-dir 0)))
420
	(error "First chars in paths %s, %s must be equal (same drive)" from-dir to-dir)
Eli Zaretskii's avatar
Eli Zaretskii committed
421 422 423 424 425 426 427 428 429
    ;else
	(let* ((from        (flymake-ensure-ends-with-slash (flymake-fix-path-name from-dir)))
	       (to          (flymake-ensure-ends-with-slash (flymake-fix-path-name to-dir)))
	       (prefix      (flymake-get-common-path-prefix from to))
	       (from-suffix (substring from (length prefix)))
	       (up-count    (length (flymake-split-string from-suffix "[/]")))
	       (to-suffix   (substring to   (length prefix)))
	       (idx         0)
	       (rel         nil))
430 431
	    (if (and (> (length to-suffix) 0) (equal "/" (char-to-string (elt to-suffix 0))))
		(setq to-suffix (substring to-suffix 1)))
Eli Zaretskii's avatar
Eli Zaretskii committed
432 433 434

	    (while (< idx up-count)
		(if (> (length rel) 0)
435
		    (setq rel (concat rel "/")))
Eli Zaretskii's avatar
Eli Zaretskii committed
436
		(setq rel (concat rel ".."))
437
		(setq idx (1+ idx)))
Eli Zaretskii's avatar
Eli Zaretskii committed
438
	    (if (> (length rel) 0)
439
		(setq rel (concat rel "/")))
Eli Zaretskii's avatar
Eli Zaretskii committed
440
	    (if (> (length to-suffix) 0)
441 442
	       (setq rel (concat rel to-suffix)))
	    (or rel "./"))))
Eli Zaretskii's avatar
Eli Zaretskii committed
443 444

(defcustom flymake-master-file-dirs '("." "./src" "./UnitTest")
445
    "Dirs where to llok for master files."
Eli Zaretskii's avatar
Eli Zaretskii committed
446
    :group 'flymake
447
    :type '(repeat (string)))
Eli Zaretskii's avatar
Eli Zaretskii committed
448 449

(defcustom flymake-master-file-count-limit 32
450
    "Max number of master files to check."
Eli Zaretskii's avatar
Eli Zaretskii committed
451
    :group 'flymake
452 453 454
    :type 'integer)

(defvar flymake-included-file-name nil " ") ; this is used to pass a parameter to a sort predicate below
Eli Zaretskii's avatar
Eli Zaretskii committed
455

456 457 458 459
(defun flymake-find-possible-master-files (file-name master-file-dirs masks)
    "Find (by name and location) all posible master files.
Mater files are .cpp and .c for and .h. Files are searched for 
starting from the .h directory and max max-level parent dirs.
Eli Zaretskii's avatar
Eli Zaretskii committed
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
File contents are not checked."
    (let* ((dir-idx    0)
	  (dir-count  (length master-file-dirs))
	  (files  nil)
	  (done   nil)
	  (masks-count (length masks)))

	(while (and (not done) (< dir-idx dir-count))
	    (let* ((dir (concat (flymake-fix-path-name (file-name-directory file-name)) "/" (nth dir-idx master-file-dirs)))
		   (masks-idx 0))
		(while (and (file-exists-p dir) (not done) (< masks-idx masks-count))
		    (let* ((mask        (nth masks-idx masks))
			   (dir-files   (directory-files dir t mask))
			   (file-count  (length dir-files))
			   (file-idx    0))

			(flymake-log 3 "dir %s, %d file(s) for mask %s" dir file-count mask)
			(while (and (not done) (< file-idx file-count))
			    (when (not (file-directory-p (nth file-idx dir-files)))
				(setq files (cons (nth file-idx dir-files) files))
				(when (>= (length files) flymake-master-file-count-limit)
				    (flymake-log 3 "master file count limit (%d) reached" flymake-master-file-count-limit)
482 483 484 485
				    (setq done t)))
			    (setq file-idx (1+ file-idx))))
		    (setq masks-idx (1+ masks-idx))))
	    (setq dir-idx (1+ dir-idx)))
Eli Zaretskii's avatar
Eli Zaretskii committed
486 487 488
	(when files
	    (setq flymake-included-file-name (file-name-nondirectory file-name))
	    (setq files (sort files 'flymake-master-file-compare))
489
	    (setq flymake-included-file-name nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
490
	(flymake-log 3 "found %d possible master file(s)" (length files))
491
	files))
Eli Zaretskii's avatar
Eli Zaretskii committed
492

493 494 495 496
(defun flymake-master-file-compare (file-one file-two)
    "Compare two files speccified by FILE-ONE and FILE-TWO.
This function is used in sort to move most possible file names
to the beginning of the list (File.h -> File.cpp moved to top."
Eli Zaretskii's avatar
Eli Zaretskii committed
497 498
    (and (equal (file-name-sans-extension flymake-included-file-name)
		(file-name-sans-extension (file-name-nondirectory file-one)))
499
	 (not (equal file-one file-two))))
Eli Zaretskii's avatar
Eli Zaretskii committed
500 501

(defcustom flymake-check-file-limit 8192
502
    "Max number of chars to look at when checking possible master file."
Eli Zaretskii's avatar
Eli Zaretskii committed
503
    :group 'flymake
504
    :type 'integer)
Eli Zaretskii's avatar
Eli Zaretskii committed
505

506
(defun flymake-check-patch-master-file-buffer (master-file-temp-buffer
Eli Zaretskii's avatar
Eli Zaretskii committed
507 508 509
					    master-file-name patched-master-file-name
					    source-file-name patched-source-file-name
					    include-dirs regexp-list)
510 511 512 513 514 515
    "Check if MASTER-FILE-NAME is a master file for SOURCE-FILE-NAME.
For .cpp master file this means it includes SOURCE-FILE-NAME (.h).
If yes, patch a copy of MASTER-FILE-NAME to include PATCHED-SOURCE-FILE-NAME
instead of SOURCE-FILE-NAME.
Whether a buffer for MATER-FILE-NAME exists, use it as a source
instead of reading master file from disk."
Eli Zaretskii's avatar
Eli Zaretskii committed
516 517 518 519 520 521 522 523 524 525 526 527 528
    (let* ((found                     nil)
	   (regexp                    (format (nth 0 regexp-list) ; "[ \t]*#[ \t]*include[ \t]*\"\\([\w0-9/\\_\.]*[/\\]*\\)\\(%s\\)\""
					      (file-name-nondirectory source-file-name)))
	   (path-idx                  (nth 1 regexp-list))
	   (name-idx                  (nth 2 regexp-list))
	   (inc-path                  nil)
	   (inc-name                  nil)
	   (search-limit              flymake-check-file-limit))
	(save-excursion
	    (unwind-protect
		(progn
		    (set-buffer master-file-temp-buffer)
		    (when (> search-limit (point-max))
529
			(setq search-limit (point-max)))
Eli Zaretskii's avatar
Eli Zaretskii committed
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
		    (flymake-log 3 "checking %s against regexp %s" master-file-name regexp)
		    (goto-char (point-min))
		    (while (and (< (point) search-limit) (re-search-forward regexp search-limit t))
						(let* ((match-beg   (match-beginning name-idx))
							   (match-end   (match-end name-idx)))

							(flymake-log 3 "found possible match for %s" (file-name-nondirectory source-file-name))
							(setq inc-path (match-string path-idx))
							(setq inc-name (match-string name-idx))
							(when (string= inc-name (file-name-nondirectory source-file-name))
								(flymake-log 3 "inc-path=%s inc-name=%s" inc-path inc-name)
								(when (flymake-check-include source-file-name inc-path inc-name include-dirs)
									(setq found t)
									; replace-match is not used here as it fails in xemacs with
									; 'last match not a buffer' error as check-includes calls replace-in-string
									(flymake-replace-region (current-buffer) match-beg match-end
546 547
															(file-name-nondirectory patched-source-file-name))))
							(forward-line 1)))
Eli Zaretskii's avatar
Eli Zaretskii committed
548
		    (when found
549
			(flymake-save-buffer-in-file (current-buffer) patched-master-file-name)))
Eli Zaretskii's avatar
Eli Zaretskii committed
550
		;+(flymake-log 3 "killing buffer %s" (buffer-name master-file-temp-buffer))
551
		(kill-buffer master-file-temp-buffer)))
Eli Zaretskii's avatar
Eli Zaretskii committed
552 553
	;+(flymake-log 3 "check-patch master file %s: %s" master-file-name found)
	(when found
554 555
	    (flymake-log 2 "found master file %s" master-file-name))
	found))
Eli Zaretskii's avatar
Eli Zaretskii committed
556

557 558
(defun flymake-replace-region (buffer beg end rep)
    "Replace text in BUFFER in region (BEG END) with REP."
Eli Zaretskii's avatar
Eli Zaretskii committed
559 560 561
    (save-excursion
	(delete-region beg end)
	    (goto-char beg)
562
	(insert rep)))
Eli Zaretskii's avatar
Eli Zaretskii committed
563

564 565
(defun flymake-read-file-to-temp-buffer (file-name)
    "Insert contents of FILE-NAME into newly created temp buffer."
Eli Zaretskii's avatar
Eli Zaretskii committed
566 567 568
    (let* ((temp-buffer (get-buffer-create (generate-new-buffer-name (concat "flymake:" (file-name-nondirectory file-name))))))
	(save-excursion
	    (set-buffer temp-buffer)
569 570
	    (insert-file-contents file-name))
	temp-buffer))
Eli Zaretskii's avatar
Eli Zaretskii committed
571

572 573
(defun flymake-copy-buffer-to-temp-buffer (buffer)
    "Copy contents of BUFFER into newly created temp buffer."
Eli Zaretskii's avatar
Eli Zaretskii committed
574 575 576 577 578 579 580 581
    (let ((contents     nil)
	  (temp-buffer  nil))
	(save-excursion
	    (set-buffer buffer)
	    (setq contents (buffer-string))

	    (setq temp-buffer (get-buffer-create (generate-new-buffer-name (concat "flymake:" (buffer-name buffer)))))
	    (set-buffer temp-buffer)
582 583
	    (insert contents))
	temp-buffer))
Eli Zaretskii's avatar
Eli Zaretskii committed
584

585 586 587
(defun flymake-check-include (source-file-name inc-path inc-name include-dirs)
    "Check if SOURCE-FILE-NAME can be found in include path.
Return t if it can be found via include path using INC-PATH and INC-NAME."
Eli Zaretskii's avatar
Eli Zaretskii committed
588 589 590 591 592 593 594 595 596
    (if (file-name-absolute-p inc-path)
	(flymake-same-files source-file-name (concat inc-path "/" inc-name))
	(let* ((count      (length include-dirs))
	       (idx        0)
	       (file-name  nil)
	       (found      nil))
	    (while (and (not found) (< idx count))
		(setq file-name (concat (file-name-directory source-file-name) "/" (nth idx include-dirs)))
		(if (> (length inc-path) 0)
597
		    (setq file-name (concat file-name "/" inc-path)))
Eli Zaretskii's avatar
Eli Zaretskii committed
598 599
		(setq file-name (concat file-name "/" inc-name))
		(when (flymake-same-files source-file-name file-name)
600 601 602
		    (setq found t))
		(setq idx (1+ idx)))
	    found)))
Eli Zaretskii's avatar
Eli Zaretskii committed
603

604 605 606
(defun flymake-find-buffer-for-file (file-name)
    "Check if there exists a buffer visiting FILE-NAME.
Return t if so, nil if not."
Eli Zaretskii's avatar
Eli Zaretskii committed
607 608
    (let ((buffer-name (get-file-buffer file-name)))
	(if buffer-name
609
	    (get-buffer buffer-name))))
Eli Zaretskii's avatar
Eli Zaretskii committed
610

611 612 613
(defun flymake-create-master-file (source-file-name patched-source-file-name get-incl-dirs-f create-temp-f masks include-regexp-list)
    "Save SOURCE-FILE-NAME with a different name.
Find master file, patch and save it."
Eli Zaretskii's avatar
Eli Zaretskii committed
614 615 616 617 618 619 620 621 622 623 624 625 626
    (let* ((possible-master-files     (flymake-find-possible-master-files source-file-name flymake-master-file-dirs masks))
	   (master-file-count         (length possible-master-files))
	   (idx                       0)
	   (temp-buffer               nil)
	   (master-file-name          nil)
	   (patched-master-file-name  nil)
	   (found                     nil))

	(while (and (not found) (< idx master-file-count))
	    (setq master-file-name (nth idx possible-master-files))
	    (setq patched-master-file-name (funcall create-temp-f master-file-name "flymake_master"))
	    (if (flymake-find-buffer-for-file master-file-name)
		(setq temp-buffer (flymake-copy-buffer-to-temp-buffer (flymake-find-buffer-for-file master-file-name)))
627
		(setq temp-buffer (flymake-read-file-to-temp-buffer master-file-name)))
Eli Zaretskii's avatar
Eli Zaretskii committed
628 629 630 631 632 633 634 635 636
	    (setq found
		  (flymake-check-patch-master-file-buffer
		       temp-buffer
		       master-file-name
		       patched-master-file-name
		       source-file-name
		       patched-source-file-name
		       (funcall get-incl-dirs-f (file-name-directory master-file-name))
		       include-regexp-list))
637
	    (setq idx (1+ idx)))
Eli Zaretskii's avatar
Eli Zaretskii committed
638 639 640 641 642
	(if found
	    (list master-file-name patched-master-file-name)
	    (progn
		(flymake-log 3 "none of %d master file(s) checked includes %s" master-file-count
			   (file-name-nondirectory source-file-name))
643
		nil))))
Eli Zaretskii's avatar
Eli Zaretskii committed
644

645
(defun flymake-save-buffer-in-file (buffer file-name)
Eli Zaretskii's avatar
Eli Zaretskii committed
646
    (or buffer
647
	(error "Invalid buffer"))
Eli Zaretskii's avatar
Eli Zaretskii committed
648 649 650 651 652
    (save-excursion
	(save-restriction
	    (set-buffer buffer)
	    (widen)
	    (make-directory (file-name-directory file-name) 1)
653 654
	    (write-region (point-min) (point-max) file-name nil 566)))
    (flymake-log 3 "saved buffer %s in file %s" (buffer-name buffer) file-name))
Eli Zaretskii's avatar
Eli Zaretskii committed
655

656 657 658
(defun flymake-save-string-to-file (file-name data)
    "Save string DATA to file FILE-NAME."
    (write-region data nil file-name nil 566))
Eli Zaretskii's avatar
Eli Zaretskii committed
659

660 661
(defun flymake-read-file-to-string (file-name)
    "Read contents of file FILE-NAME and return as a string."
Eli Zaretskii's avatar
Eli Zaretskii committed
662 663
    (with-temp-buffer
	(insert-file-contents file-name)
664
	(buffer-substring (point-min) (point-max))))
Eli Zaretskii's avatar
Eli Zaretskii committed
665

666 667 668
(defun flymake-process-filter (process output)
    "Parse OUTPUT and highlight error lines.
It's flymake process filter."
Eli Zaretskii's avatar
Eli Zaretskii committed
669 670 671 672 673
    (let* ((pid               (process-id process))
	   (source-buffer     (get-buffer (flymake-get-source-buffer-name pid))))

	(flymake-log 3 "received %d byte(s) of output from process %d" (length output) pid)
	(when source-buffer
674
	    (flymake-parse-output-and-residual source-buffer output))))
Eli Zaretskii's avatar
Eli Zaretskii committed
675

676 677
(defun flymake-process-sentinel (process event)
   "Sentinel for syntax check buffers."
Eli Zaretskii's avatar
Eli Zaretskii committed
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
   (if (memq (process-status process) '(signal exit))
       (let*((exit-status       (process-exit-status process))
	     (command           (process-command process))
	     (pid               (process-id process))
	     (source-buffer     (get-buffer (flymake-get-source-buffer-name pid)))
	     (cleanup-f         (flymake-get-cleanup-function (buffer-file-name source-buffer))))

	 (flymake-log 2 "process %d exited with code %d" pid exit-status)
	 (condition-case err
	     (progn
		 (flymake-log 3 "cleaning up using %s" cleanup-f)
		 (funcall cleanup-f source-buffer)

		 (flymake-unreg-names pid)
		 (delete-process process)

		 (when source-buffer
		     (save-excursion
			 (set-buffer source-buffer)

			 (flymake-parse-residual source-buffer)
699
			 (flymake-post-syntax-check source-buffer exit-status command)
700
			 (flymake-set-buffer-is-running source-buffer nil))))
Eli Zaretskii's avatar
Eli Zaretskii committed
701 702 703 704
	   (error
	      (let ((err-str (format "Error in process sentinel for buffer %s: %s"
				    source-buffer (error-message-string err))))
		  (flymake-log 0 err-str)
705
		  (flymake-set-buffer-is-running source-buffer nil)))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
706

707
(defun flymake-post-syntax-check (source-buffer exit-status command)
Eli Zaretskii's avatar
Eli Zaretskii committed
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
   (flymake-set-buffer-err-info source-buffer (flymake-get-buffer-new-err-info source-buffer))
   (flymake-set-buffer-new-err-info source-buffer nil)

   (flymake-set-buffer-err-info source-buffer (flymake-fix-line-numbers
					     (flymake-get-buffer-err-info source-buffer)
					     1
					     (flymake-count-lines source-buffer)))
   (flymake-delete-own-overlays source-buffer)
   (flymake-highlight-err-lines source-buffer (flymake-get-buffer-err-info source-buffer))

   (let ((err-count   (flymake-get-err-count (flymake-get-buffer-err-info source-buffer) "e"))
	 (warn-count  (flymake-get-err-count (flymake-get-buffer-err-info source-buffer) "w")))

       (flymake-log 2 "%s: %d error(s), %d warning(s) in %.2f second(s)"
		  (buffer-name source-buffer) err-count warn-count
		  (- (flymake-float-time) (flymake-get-buffer-check-start-time source-buffer)))
       (flymake-set-buffer-check-start-time source-buffer nil)
       (if (and (equal 0 err-count) (equal 0 warn-count))
	   (if (equal 0 exit-status)
	       (flymake-report-status source-buffer "" "") ; PASSED
	       (if (not (flymake-get-buffer-check-was-interrupted source-buffer))
		   (flymake-report-fatal-status (current-buffer) "CFGERR"
		       (format "Configuration error has occured while running %s" command))
731 732
		   (flymake-report-status source-buffer nil ""))) ; "STOPPED"
	   (flymake-report-status source-buffer (format "%d/%d" err-count warn-count) ""))))
Eli Zaretskii's avatar
Eli Zaretskii committed
733

734 735
(defun flymake-parse-output-and-residual (source-buffer output)
    "Split OUTPUT into lines, merge in residual if necessary."
Eli Zaretskii's avatar
Eli Zaretskii committed
736 737 738 739 740 741 742 743 744 745 746
    (save-excursion
	(set-buffer source-buffer)
	(let* ((buffer-residual     (flymake-get-buffer-output-residual source-buffer))
	       (total-output        (if buffer-residual (concat buffer-residual output) output))
	       (lines-and-residual  (flymake-split-output total-output))
	       (lines               (nth 0 lines-and-residual))
	       (new-residual        (nth 1 lines-and-residual)))

	    (flymake-set-buffer-output-residual source-buffer new-residual)
	    (flymake-set-buffer-new-err-info source-buffer (flymake-parse-err-lines
							    (flymake-get-buffer-new-err-info source-buffer)
747
							    source-buffer lines)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
748

749 750
(defun flymake-parse-residual (source-buffer)
    "Parse residual if it's non empty."
Eli Zaretskii's avatar
Eli Zaretskii committed
751 752 753 754 755 756 757
    (save-excursion
	(set-buffer source-buffer)
	(when (flymake-get-buffer-output-residual source-buffer)
	    (flymake-set-buffer-new-err-info source-buffer (flymake-parse-err-lines
							   (flymake-get-buffer-new-err-info source-buffer)
							   source-buffer
							   (list (flymake-get-buffer-output-residual source-buffer))))
758
	    (flymake-set-buffer-output-residual source-buffer nil))))
Eli Zaretskii's avatar
Eli Zaretskii committed
759 760

(defvar flymake-err-info nil
761 762
  "Sorted list of line numbers and lists of err info in the form (file, err-text).")

Eli Zaretskii's avatar
Eli Zaretskii committed
763
(make-variable-buffer-local 'flymake-err-info)
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778

(defun flymake-get-buffer-err-info (buffer)
    (flymake-get-buffer-var buffer 'flymake-err-info))

(defun flymake-set-buffer-err-info (buffer err-info)
    (flymake-set-buffer-var buffer 'flymake-err-info err-info))

(defun flymake-er-make-er (line-no line-err-info-list)
    (list line-no line-err-info-list))

(defun flymake-er-get-line (err-info)
    (nth 0 err-info))

(defun flymake-er-get-line-err-info-list (err-info)
    (nth 1 err-info))
Eli Zaretskii's avatar
Eli Zaretskii committed
779 780

(defvar flymake-new-err-info nil
781 782
  "Same as 'flymake-err-info', effective when a syntax check is in progress.")

Eli Zaretskii's avatar
Eli Zaretskii committed
783
(make-variable-buffer-local 'flymake-new-err-info)
784 785 786 787 788 789

(defun flymake-get-buffer-new-err-info (buffer)
    (flymake-get-buffer-var buffer 'flymake-new-err-info))

(defun flymake-set-buffer-new-err-info (buffer new-err-info)
    (flymake-set-buffer-var buffer 'flymake-new-err-info new-err-info))
Eli Zaretskii's avatar
Eli Zaretskii committed
790 791

;; getters/setters for line-err-info: (file, line, type, text).
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
(defun flymake-ler-make-ler (file line type text &optional full-file)
    (list file line type text full-file))

(defun flymake-ler-get-file (line-err-info)
    (nth 0 line-err-info))

(defun flymake-ler-get-line (line-err-info)
    (nth 1 line-err-info))

(defun flymake-ler-get-type (line-err-info)
    (nth 2 line-err-info))

(defun flymake-ler-get-text (line-err-info)
    (nth 3 line-err-info))

(defun flymake-ler-get-full-file (line-err-info)
    (nth 4 line-err-info))

(defun flymake-ler-set-file (line-err-info file)
Eli Zaretskii's avatar
Eli Zaretskii committed
811 812 813 814
    (flymake-ler-make-ler file
			(flymake-ler-get-line line-err-info)
			(flymake-ler-get-type line-err-info)
			(flymake-ler-get-text line-err-info)
815 816 817
			(flymake-ler-get-full-file line-err-info)))

(defun flymake-ler-set-full-file (line-err-info full-file)
Eli Zaretskii's avatar
Eli Zaretskii committed
818 819 820 821
    (flymake-ler-make-ler (flymake-ler-get-file line-err-info)
			(flymake-ler-get-line line-err-info)
			(flymake-ler-get-type line-err-info)
			(flymake-ler-get-text line-err-info)
822 823 824
			full-file))

(defun flymake-ler-set-line (line-err-info line)
Eli Zaretskii's avatar
Eli Zaretskii committed
825 826 827 828
    (flymake-ler-make-ler (flymake-ler-get-file line-err-info)
			line
			(flymake-ler-get-type line-err-info)
			(flymake-ler-get-text line-err-info)
829
			(flymake-ler-get-full-file line-err-info)))
Eli Zaretskii's avatar
Eli Zaretskii committed
830

831 832 833
(defun flymake-get-line-err-count (line-err-info-list type)
    "Return number of errors of specified TYPE.
Value of TYPE is eigher e or w."
Eli Zaretskii's avatar
Eli Zaretskii committed
834 835 836 837 838 839
    (let* ((idx        0)
	   (count      (length line-err-info-list))
	   (err-count  0))

	(while (< idx count)
	    (when (equal type (flymake-ler-get-type (nth idx line-err-info-list)))
840 841 842
		(setq err-count (1+ err-count)))
	    (setq idx (1+ idx)))
	err-count))
Eli Zaretskii's avatar
Eli Zaretskii committed
843

844 845
(defun flymake-get-err-count (err-info-list type)
    "Return number of errors of specified TYPE for ERR-INFO-LIST."
Eli Zaretskii's avatar
Eli Zaretskii committed
846 847 848 849 850
    (let* ((idx        0)
	   (count      (length err-info-list))
	   (err-count  0))
	(while (< idx count)
	    (setq err-count (+ err-count (flymake-get-line-err-count (nth 1 (nth idx err-info-list)) type)))
851 852 853 854 855 856 857 858 859
	    (setq idx (1+ idx)))
	err-count))

(defun flymake-fix-line-numbers (err-info-list min-line max-line)
    "Replace line numbers with fixed value.
If line-numbers is less than MIN-LINE, set line numbers to MIN-LINE.
If line numbers is greater than MAX-LINE, set line numbers to MAX-LINE.
The reason for this fix is because some compilers might report 
line number outside the file being compiled."
Eli Zaretskii's avatar
Eli Zaretskii committed
860 861 862 863 864 865 866 867 868
    (let* ((count     (length err-info-list))
	   (err-info  nil)
	   (line      0))
	(while (> count 0)
	    (setq err-info (nth (1- count) err-info-list))
	    (setq line (flymake-er-get-line err-info))
	    (when (or (< line min-line) (> line max-line))
		(setq line (if (< line min-line) min-line max-line))
		(setq err-info-list (flymake-set-at err-info-list (1- count)
869 870 871 872
		    (flymake-er-make-er line
			(flymake-er-get-line-err-info-list err-info)))))
	    (setq count (1- count))))
    err-info-list)
Eli Zaretskii's avatar
Eli Zaretskii committed
873

874 875
(defun flymake-highlight-err-lines (buffer err-info-list)
    "Highlight error lines in BUFFER using info from ERR-INFO-LIST."
Eli Zaretskii's avatar
Eli Zaretskii committed
876 877 878 879 880 881
    (save-excursion
	(set-buffer buffer)
	(let* ((idx    0)
	       (count  (length err-info-list)))
	    (while (< idx count)
		(flymake-highlight-line (car (nth idx err-info-list)) (nth 1 (nth idx err-info-list)))
882
		(setq idx (1+ idx))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
883

884 885 886
(defun flymake-overlay-p (ov)
     "Determine whether overlay OV was created by flymake."
     (and (overlayp ov) (overlay-get ov 'flymake-overlay)))
Eli Zaretskii's avatar
Eli Zaretskii committed
887

888 889
(defun flymake-make-overlay (beg end tooltip-text face mouse-face)
    "Allocate a flymake overlay in range BEG and END."
Eli Zaretskii's avatar
Eli Zaretskii committed
890 891 892 893 894 895 896 897
    (when (not (flymake-region-has-flymake-overlays beg end))
	(let ((ov (make-overlay beg end nil t t)))
	    (overlay-put ov 'face           face)
	    (overlay-put ov 'mouse-face     mouse-face)
	    (overlay-put ov 'help-echo      tooltip-text)
	    (overlay-put ov 'flymake-overlay  t)
	    (overlay-put ov 'priority 100)
	    ;+(flymake-log 3 "created overlay %s" ov)
898 899
	    ov)
	(flymake-log 3 "created an overlay at (%d-%d)" beg end)))
Eli Zaretskii's avatar
Eli Zaretskii committed
900

901 902
(defun flymake-delete-own-overlays (buffer)
    "Delete all flymake overlays in BUFFER."
Eli Zaretskii's avatar
Eli Zaretskii committed
903 904 905 906 907 908 909 910
    (save-excursion
	(set-buffer buffer)
	(let ((ov (overlays-in (point-min) (point-max))))
	    (while (consp ov)
		(when (flymake-overlay-p (car ov))
		    (delete-overlay (car ov))
		    ;+(flymake-log 3 "deleted overlay %s" ov)
		)
911
		(setq ov (cdr ov))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
912

913 914 915
(defun flymake-region-has-flymake-overlays (beg end)
    "Check if region specified by BEG and END has overlay.
Return t if it has at least one flymake overlay, nil if no overlay."
Eli Zaretskii's avatar
Eli Zaretskii committed
916 917 918 919
    (let ((ov                  (overlays-in beg end))
	  (has-flymake-overlays  nil))
	(while (consp ov)
	    (when (flymake-overlay-p (car ov))
920 921
		(setq has-flymake-overlays t))
	    (setq ov (cdr ov)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
922 923 924 925 926 927

(defface flymake-errline-face
;+   '((((class color)) (:foreground "OrangeRed" :bold t :underline t))
;+   '((((class color)) (:underline "OrangeRed"))
   '((((class color)) (:background "LightPink"))
     (t (:bold t)))
928 929
   "Face used for marking error lines."
    :group 'flymake)
Eli Zaretskii's avatar
Eli Zaretskii committed
930 931 932 933

(defface flymake-warnline-face
   '((((class color)) (:background "LightBlue2"))
     (t (:bold t)))
934 935
   "Face used for marking warning lines."
    :group 'flymake)
Eli Zaretskii's avatar
Eli Zaretskii committed
936

937 938 939
(defun flymake-highlight-line (line-no line-err-info-list)
    "Highlight line LINE-NO in current buffer.
Perhaps use text from LINE-ERR-INFO-ILST to enhance highlighting."
Eli Zaretskii's avatar
Eli Zaretskii committed
940 941 942 943 944 945 946 947 948 949
    (goto-line line-no)
    (let* ((line-beg (flymake-line-beginning-position))
	   (line-end (flymake-line-end-position))
	   (beg      line-beg)
	   (end      line-end)
	   (tooltip-text (flymake-ler-get-text (nth 0 line-err-info-list)))
	   (face     nil))

	(goto-char line-beg)
	(while (looking-at "[ \t]")
950
	    (forward-char))
Eli Zaretskii's avatar
Eli Zaretskii committed
951 952 953 954 955

	(setq beg (point))

	(goto-char line-end)
	(while (and (looking-at "[ \t\r\n]") (> (point) 1))
956
	    (backward-char))
Eli Zaretskii's avatar
Eli Zaretskii committed
957 958 959 960 961

	(setq end (1+ (point)))

	(when (<= end beg)
	    (setq beg line-beg)
962 963
	    (setq end line-end))

Eli Zaretskii's avatar
Eli Zaretskii committed
964 965 966
	(when (= end beg)
	    (goto-char end)
	    (forward-line)
967 968
	    (setq end (point)))

Eli Zaretskii's avatar
Eli Zaretskii committed
969 970
	(if (> (flymake-get-line-err-count line-err-info-list "e") 0)
	    (setq face 'flymake-errline-face)
971 972 973
	    (setq face 'flymake-warnline-face))

	(flymake-make-overlay beg end tooltip-text face nil)))
Eli Zaretskii's avatar
Eli Zaretskii committed
974

975 976
(defun flymake-parse-err-lines (err-info-list source-buffer lines)
    "Parse err LINES, store info in ERR-INFO-LIST."
Eli Zaretskii's avatar
Eli Zaretskii committed
977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
    (let* ((count              (length lines))
	   (idx                0)
	   (line-err-info      nil)
	   (real-file-name     nil)
	   (source-file-name   (buffer-file-name source-buffer))
	   (get-real-file-name-f (flymake-get-real-file-name-function source-file-name)))

	(while (< idx count)
	    (setq line-err-info (flymake-parse-line (nth idx lines)))
	    (when line-err-info
		(setq real-file-name (funcall get-real-file-name-f source-buffer (flymake-ler-get-file line-err-info)))
		(setq line-err-info (flymake-ler-set-full-file line-err-info real-file-name))

		(if (flymake-same-files real-file-name source-file-name)
		    (setq line-err-info (flymake-ler-set-file line-err-info nil))
992
		    (setq line-err-info (flymake-ler-set-file line-err-info (file-name-nondirectory real-file-name))))
Eli Zaretskii's avatar
Eli Zaretskii committed
993

994
		(setq err-info-list (flymake-add-err-info err-info-list line-err-info)))
Eli Zaretskii's avatar
Eli Zaretskii committed
995
	    (flymake-log 3 "parsed '%s', %s line-err-info" (nth idx lines) (if line-err-info "got" "no"))
996 997
	    (setq idx (1+ idx)))
	err-info-list))
Eli Zaretskii's avatar
Eli Zaretskii committed
998

999 1000 1001
(defun flymake-split-output (output)
    "Split OUTPUT into lines.
Return last one as residual if it does not end with newline char. Returns ((lines) residual)."
Eli Zaretskii's avatar
Eli Zaretskii committed
1002 1003 1004 1005 1006 1007
    (when (and output (> (length output) 0))
	(let* ((lines (flymake-split-string output "[\n\r]+"))
	       (complete (equal "\n" (char-to-string (aref output (1- (length output))))))
	       (residual nil))
	    (when (not complete)
		(setq residual (car (last lines)))
1008 1009
		(setq lines (butlast lines)))
	    (list lines residual))))
Eli Zaretskii's avatar
Eli Zaretskii committed
1010

1011 1012 1013
(defun flymake-reformat-err-line-patterns-from-compile-el (original-list)
    "Grab error line patterns from ORIGINAL-LIST in compile.el format.
Convert it to flymake internal format."
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
	(let* ((converted-list '()))
	(mapcar
	    (lambda (item)
			(setq item (cdr item))
			(let ((regexp (nth 0 item))
				  (file (nth 1 item))
				  (line (nth 2 item))
				  (col (nth 3 item))
				  end-line)
			  (if (consp file)	(setq file (car file)))
			  (if (consp line)	(setq end-line (cdr line) line (car line)))
			  (if (consp col)	(setq col (car col)))

			  (when (not (functionp line))
1028 1029 1030 1031 1032 1033
				  (setq converted-list (cons (list regexp file line col) converted-list)))))
		original-list)
	converted-list))

(eval-when-compile
    (require 'compile))
1034

Eli Zaretskii's avatar
Eli Zaretskii committed
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
(defvar flymake-err-line-patterns  ; regexp file-idx line-idx col-idx (optional) text-idx(optional), match-end to end of string is error text
    (append
     '(
    ; MS Visual C++ 6.0
       ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\)) \: \\(\\(error\\|warning\\|fatal error\\) \\(C[0-9]+\\):[ \t\n]*\\(.+\\)\\)"
	1 3 nil 4)
    ; jikes
       ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)\:\\([0-9]+\\)\:[0-9]+\:[0-9]+\:[0-9]+\: \\(\\(Error\\|Warning\\|Caution\\|Semantic Error\\):[ \t\n]*\\(.+\\)\\)"
	1 3 nil 4)
    ; MS midl
       ("midl[ ]*:[ ]*\\(command line error .*\\)"
	nil nil nil 1)
    ; MS C#
       ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\),[0-9]+)\: \\(\\(error\\|warning\\|fatal error\\) \\(CS[0-9]+\\):[ \t\n]*\\(.+\\)\\)"
	1 3 nil 4)
    ; perl
       ("\\(.*\\) at \\([^ \n]+\\) line \\([0-9]+\\)[,.\n]" 2 3 nil 1)
    ; LaTeX warnings (fileless) ("\\(LaTeX \\(Warning\\|Error\\): .*\\) on input line \\([0-9]+\\)" 20 3 nil 1)
    ; ant/javac
	   (" *\\(\\[javac\\]\\)? *\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)\:\\([0-9]+\\)\:[ \t\n]*\\(.+\\)"
1055
	2 4 nil 5))
1056 1057
	 ;; compilation-error-regexp-alist)
     (flymake-reformat-err-line-patterns-from-compile-el compilation-error-regexp-alist-alist)) 
1058 1059
    "patterns for matching error/warning lines, (regexp file-idx line-idx err-text-idx). Use flymake-reformat-err-line-patterns-from-compile-el to add patterns from compile.el")

Eli Zaretskii's avatar
Eli Zaretskii committed
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
;(defcustom flymake-err-line-patterns
;  '(
;    ; MS Visual C++ 6.0
;    ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\)) \: \\(\\(error\\|warning\\|fatal error\\) \\(C[0-9]+\\):[ \t\n]*\\(.+\\)\\)"
;       1 3 4)
;   ; jikes
;   ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)\:\\([0-9]+\\)\:[0-9]+\:[0-9]+\:[0-9]+\: \\(\\(Error\\|Warning\\|Caution\\):[ \t\n]*\\(.+\\)\\)"
;       1 3 4))
;    "patterns for matching error/warning lines, (regexp file-idx line-idx err-text-idx)"
;   :group 'flymake
;   :type '(repeat (string number number number))
;)

1073 1074 1075
(defun flymake-parse-line (line)
    "Parse LINE to see if it is an error of warning.
Return its components if so, nil if