flymake.el 80.2 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
;;; 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
Richard M. Stallman's avatar
Richard M. Stallman committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
  (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)))
   (t                              (makehash test))))

(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))

(defalias 'flymake-split-string 'flymake-split-string-remove-empty-edges)
Eli Zaretskii's avatar
Eli Zaretskii committed
81 82

(defun flymake-get-temp-dir()
Richard M. Stallman's avatar
Richard M. Stallman committed
83 84 85
  (cond
   ((equal flymake-emacs 'xemacs)  (temp-directory))
   (t                              temporary-file-directory)))
Eli Zaretskii's avatar
Eli Zaretskii committed
86

87
(defun flymake-line-beginning-position ()
Richard M. Stallman's avatar
Richard M. Stallman committed
88 89 90
  (save-excursion
    (beginning-of-line)
    (point)))
Eli Zaretskii's avatar
Eli Zaretskii committed
91

92
(defun flymake-line-end-position ()
Richard M. Stallman's avatar
Richard M. Stallman committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
  (save-excursion
    (end-of-line)
    (point)))

(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))
      )
    )
   (t (x-popup-menu pos (flymake-make-emacs-menu menu-data)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
109

Richard M. Stallman's avatar
Richard M. Stallman committed
110 111 112 113
(defun flymake-make-emacs-menu (menu-data)
  (let* ((menu-title     (nth 0 menu-data))
	 (menu-items     (nth 1 menu-data))
	 (menu-commands  nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
114

Richard M. Stallman's avatar
Richard M. Stallman committed
115 116 117 118
    (setq menu-commands (mapcar (lambda (foo)
				  (cons (nth 0 foo) (nth 1 foo)))
				menu-items))
    (list menu-title (cons "" menu-commands))))
Eli Zaretskii's avatar
Eli Zaretskii committed
119

120
(defun flymake-nop ())
Eli Zaretskii's avatar
Eli Zaretskii committed
121

122
(defun flymake-make-xemacs-menu (menu-data)
Richard M. Stallman's avatar
Richard M. Stallman committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
  (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))
    (cons menu-title menu-commands)))

(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 number 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))
   )
  )
Eli Zaretskii's avatar
Eli Zaretskii committed
158 159 160 161

;;;; ]]

(defcustom flymake-log-level -1
Richard M. Stallman's avatar
Richard M. Stallman committed
162
  "Logging level, only messages with level > flymake-log-level will not be logged
Eli Zaretskii's avatar
Eli Zaretskii committed
163
-1 = NONE, 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG"
Richard M. Stallman's avatar
Richard M. Stallman committed
164 165
  :group 'flymake
  :type 'integer)
Eli Zaretskii's avatar
Eli Zaretskii committed
166

167
(defun flymake-log (level text &rest args)
Richard M. Stallman's avatar
Richard M. Stallman committed
168 169 170 171 172 173 174 175 176
  "Log a message with optional arguments."
  (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
	;;)
177
	)))
Eli Zaretskii's avatar
Eli Zaretskii committed
178

179
(defun flymake-ins-after (list pos val)
Richard M. Stallman's avatar
Richard M. Stallman committed
180 181 182 183
  "Insert VAL into LIST after position POS."
  (let ((tmp (copy-sequence list)))	; (???)
    (setcdr (nthcdr pos tmp) (cons val (nthcdr (1+ pos) tmp)))
    tmp))
Eli Zaretskii's avatar
Eli Zaretskii committed
184

185
(defun flymake-set-at (list pos val)
Richard M. Stallman's avatar
Richard M. Stallman committed
186 187 188 189
  "Set VAL at position POS in LIST"
  (let ((tmp (copy-sequence list)))	; (???)
    (setcar (nthcdr pos tmp) val)
    tmp))
Eli Zaretskii's avatar
Eli Zaretskii committed
190

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

194
(defun flymake-reg-names (pid source-buffer-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
195 196 197 198
  "Save into in PID map."
  (unless (stringp source-buffer-name)
    (error "Invalid buffer name"))
  (puthash pid (list source-buffer-name) flymake-pid-to-names))
Eli Zaretskii's avatar
Eli Zaretskii committed
199

200
(defun flymake-get-source-buffer-name (pid)
Richard M. Stallman's avatar
Richard M. Stallman committed
201 202
  "Return buffer name stored in PID map."
  (nth 0 (gethash pid flymake-pid-to-names)))
Eli Zaretskii's avatar
Eli Zaretskii committed
203

204
(defun flymake-unreg-names (pid)
Richard M. Stallman's avatar
Richard M. Stallman committed
205 206
  "Delete PID->buffer name mapping."
  (remhash pid flymake-pid-to-names))
Eli Zaretskii's avatar
Eli Zaretskii committed
207

208
(defun flymake-get-buffer-var (buffer var-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
209 210 211
  "Switch to BUFFER if necessary and return local variable VAR-NAME."
  (unless (bufferp buffer)
    (error "Invalid buffer"))
Eli Zaretskii's avatar
Eli Zaretskii committed
212

Richard M. Stallman's avatar
Richard M. Stallman committed
213 214 215 216 217
  (if (eq buffer (current-buffer))
      (symbol-value var-name)
    (save-excursion
      (set-buffer buffer)
      (symbol-value var-name))))
Eli Zaretskii's avatar
Eli Zaretskii committed
218

219
(defun flymake-set-buffer-var (buffer var-name var-value)
Richard M. Stallman's avatar
Richard M. Stallman committed
220 221 222
  "Switch to BUFFER if necessary and set local variable VAR-NAME to VAR-VALUE."
  (unless (bufferp buffer)
    (error "Invalid buffer"))
Eli Zaretskii's avatar
Eli Zaretskii committed
223

Richard M. Stallman's avatar
Richard M. Stallman committed
224 225 226 227 228
  (if (eq buffer (current-buffer))
      (set var-name var-value)
    (save-excursion
      (set-buffer buffer)
      (set var-name var-value))))
229 230 231

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

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

235
(defun flymake-get-buffer-data (buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
236
  (flymake-get-buffer-var buffer 'flymake-buffer-data))
237 238

(defun flymake-set-buffer-data (buffer data)
Richard M. Stallman's avatar
Richard M. Stallman committed
239
  (flymake-set-buffer-var buffer 'flymake-buffer-data data))
240 241

(defun flymake-get-buffer-value (buffer name)
Richard M. Stallman's avatar
Richard M. Stallman committed
242
  (gethash name (flymake-get-buffer-data buffer)))
243 244

(defun flymake-set-buffer-value (buffer name value)
Richard M. Stallman's avatar
Richard M. Stallman committed
245
  (puthash name value (flymake-get-buffer-data buffer)))
246 247 248

(defvar flymake-output-residual nil "")

Eli Zaretskii's avatar
Eli Zaretskii committed
249
(make-variable-buffer-local 'flymake-output-residual)
250 251

(defun flymake-get-buffer-output-residual (buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
252
  (flymake-get-buffer-var buffer 'flymake-output-residual))
253 254

(defun flymake-set-buffer-output-residual (buffer residual)
Richard M. Stallman's avatar
Richard M. Stallman committed
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
  (flymake-set-buffer-var buffer 'flymake-output-residual residual))

(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)
    )
  "*Files syntax checking is allowed for."
  :group 'flymake
  :type '(repeat (string symbol symbol symbol)))
Eli Zaretskii's avatar
Eli Zaretskii committed
282

283
(defun flymake-get-file-name-mode-and-masks (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
284 285 286 287 288 289 290 291 292 293 294 295
  "Return the corresponding entry from 'flymake-allowed-file-name-masks'."
  (unless (stringp file-name)
    (error "Invalid file-name"))
  (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)
	  (setq mode-and-masks (cdr (nth idx flymake-allowed-file-name-masks))))
      (setq idx (1+ idx)))
    (flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks))
    mode-and-masks))
Eli Zaretskii's avatar
Eli Zaretskii committed
296

297
(defun flymake-can-syntax-check-file (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
298
  "Determine whether we can syntax check FILE-NAME.
299
Return nil if we cannot, non-nil if we can."
Richard M. Stallman's avatar
Richard M. Stallman committed
300
  (if (flymake-get-init-function file-name) t nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
301

302
(defun flymake-get-init-function (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
303 304 305 306 307
  "Return init function to be used for the file."
  (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))
    init-f))
Eli Zaretskii's avatar
Eli Zaretskii committed
308

309
(defun flymake-get-cleanup-function (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
310 311
  "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
312

313
(defun flymake-get-real-file-name-function (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
314
  (or (nth 2 (flymake-get-file-name-mode-and-masks file-name)) 'flymake-get-real-file-name))
Eli Zaretskii's avatar
Eli Zaretskii committed
315 316

(defcustom flymake-buildfile-dirs '("." ".." "../.." "../../.." "../../../.." "../../../../.." "../../../../../.." "../../../../../../.." "../../../../../../../.." "../../../../../../../../.." "../../../../../../../../../.." "../../../../../../../../../../..")
Richard M. Stallman's avatar
Richard M. Stallman committed
317 318 319
  "Dirs to look for buildfile."
  :group 'flymake
  :type '(repeat (string)))
Eli Zaretskii's avatar
Eli Zaretskii committed
320 321 322

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

323
(defun flymake-get-buildfile-from-cache (dir-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
324
  (gethash dir-name flymake-find-buildfile-cache))
325 326

(defun flymake-add-buildfile-to-cache (dir-name buildfile)
Richard M. Stallman's avatar
Richard M. Stallman committed
327
  (puthash dir-name buildfile flymake-find-buildfile-cache))
328 329

(defun flymake-clear-buildfile-cache ()
Richard M. Stallman's avatar
Richard M. Stallman committed
330
  (clrhash flymake-find-buildfile-cache))
331 332

(defun flymake-find-buildfile (buildfile-name source-dir-name dirs)
Richard M. Stallman's avatar
Richard M. Stallman committed
333
  "Find buildfile starting from current directory.
334
Buildfile includes Makefile, build.xml etc.
Richard M. Stallman's avatar
Richard M. Stallman committed
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
Return its file name if found, or nil if not found."
  (if (flymake-get-buildfile-from-cache source-dir-name)
      (progn
	(flymake-get-buildfile-from-cache source-dir-name))
    (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)
	  (setq found t))
	(setq dir-idx (1+ dir-idx)))
      (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)
	    buildfile-dir)
Eli Zaretskii's avatar
Eli Zaretskii committed
355
	(progn
Richard M. Stallman's avatar
Richard M. Stallman committed
356 357 358 359 360 361 362 363 364 365 366 367
	  (flymake-log 3 "buildfile for %s not found" source-dir-name)
	  nil)))))

(defun flymake-fix-file-name (name)
  "Replace all occurences of '\' with '/'."
  (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))
	  (setq new-name (substring new-name 0 (1- (length new-name)))))
      new-name)))
Eli Zaretskii's avatar
Eli Zaretskii committed
368

369
(defun flymake-same-files (file-name-one file-name-two)
Richard M. Stallman's avatar
Richard M. Stallman committed
370
  "Check if FILE-NAME-ONE and FILE-NAME-TWO point to same file.
371
Return t if so, nil if not."
Richard M. Stallman's avatar
Richard M. Stallman committed
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
  (equal (flymake-fix-file-name file-name-one)
	 (flymake-fix-file-name file-name-two)))

(defun flymake-ensure-ends-with-slash (filename)
  ;; Should this really be file-name-as-directory?
  (if (not (= (elt filename (1- (length filename))) (string-to-char "/")))
      (concat filename "/")
    filename))

(defun flymake-get-common-file-prefix (string-one string-two)
  "Return common prefix for two file names STRING-ONE and STRING-TWO."
  (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
	      (setq prefix (substring string-one 0 (1+ slash-pos-one))))
	  (setq done t)))
      prefix)))

(defun flymake-build-relative-filename (from-dir to-dir)
  "Return rel: FROM-DIR/rel == TO-DIR."
  (if (not (equal (elt from-dir 0) (elt to-dir 0)))
      (error "First chars in file names %s, %s must be equal (same drive)"
	     from-dir to-dir)
    (let* ((from (flymake-ensure-ends-with-slash (flymake-fix-file-name from-dir)))
	   (to   (flymake-ensure-ends-with-slash (flymake-fix-file-name to-dir)))
	   (prefix      (flymake-get-common-file-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))
      (if (and (> (length to-suffix) 0) (equal "/" (char-to-string (elt to-suffix 0))))
	  (setq to-suffix (substring to-suffix 1)))

      (while (< idx up-count)
	(if (> (length rel) 0)
	    (setq rel (concat rel "/")))
	(setq rel (concat rel ".."))
	(setq idx (1+ idx)))
      (if (> (length rel) 0)
	  (setq rel (concat rel "/")))
      (if (> (length to-suffix) 0)
	  (setq rel (concat rel to-suffix)))
      (or rel "./"))))
Eli Zaretskii's avatar
Eli Zaretskii committed
427 428

(defcustom flymake-master-file-dirs '("." "./src" "./UnitTest")
Richard M. Stallman's avatar
Richard M. Stallman committed
429 430 431
  "Dirs where to llok for master files."
  :group 'flymake
  :type '(repeat (string)))
Eli Zaretskii's avatar
Eli Zaretskii committed
432 433

(defcustom flymake-master-file-count-limit 32
Richard M. Stallman's avatar
Richard M. Stallman committed
434 435 436
  "Max number of master files to check."
  :group 'flymake
  :type 'integer)
437 438

(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
439

440
(defun flymake-find-possible-master-files (file-name master-file-dirs masks)
Richard M. Stallman's avatar
Richard M. Stallman committed
441
  "Find (by name and location) all posible master files.
442 443
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
444
File contents are not checked."
Richard M. Stallman's avatar
Richard M. Stallman committed
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
  (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-file-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)
		  (setq done t)))
	      (setq file-idx (1+ file-idx))))
	  (setq masks-idx (1+ masks-idx))))
      (setq dir-idx (1+ dir-idx)))
    (when files
      (setq flymake-included-file-name (file-name-nondirectory file-name))
      (setq files (sort files 'flymake-master-file-compare))
      (setq flymake-included-file-name nil))
    (flymake-log 3 "found %d possible master file(s)" (length files))
    files))
Eli Zaretskii's avatar
Eli Zaretskii committed
476

477
(defun flymake-master-file-compare (file-one file-two)
Richard M. Stallman's avatar
Richard M. Stallman committed
478
  "Compare two files speccified by FILE-ONE and FILE-TWO.
479 480
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."
Richard M. Stallman's avatar
Richard M. Stallman committed
481 482 483
  (and (equal (file-name-sans-extension flymake-included-file-name)
	      (file-name-sans-extension (file-name-nondirectory file-one)))
       (not (equal file-one file-two))))
Eli Zaretskii's avatar
Eli Zaretskii committed
484 485

(defcustom flymake-check-file-limit 8192
Richard M. Stallman's avatar
Richard M. Stallman committed
486 487 488
  "Max number of chars to look at when checking possible master file."
  :group 'flymake
  :type 'integer)
Eli Zaretskii's avatar
Eli Zaretskii committed
489

490
(defun flymake-check-patch-master-file-buffer (master-file-temp-buffer
Richard M. Stallman's avatar
Richard M. Stallman committed
491 492 493 494
					       master-file-name patched-master-file-name
					       source-file-name patched-source-file-name
					       include-dirs regexp-list)
  "Check if MASTER-FILE-NAME is a master file for SOURCE-FILE-NAME.
495 496 497 498 499
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."
Richard M. Stallman's avatar
Richard M. Stallman committed
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
  (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))
	      (setq search-limit (point-max)))
	    (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
					    (file-name-nondirectory patched-source-file-name))))
		(forward-line 1)))
	    (when found
	      (flymake-save-buffer-in-file (current-buffer) patched-master-file-name)))
					;+(flymake-log 3 "killing buffer %s" (buffer-name master-file-temp-buffer))
	(kill-buffer master-file-temp-buffer)))
					;+(flymake-log 3 "check-patch master file %s: %s" master-file-name found)
    (when found
      (flymake-log 2 "found master file %s" master-file-name))
    found))
Eli Zaretskii's avatar
Eli Zaretskii committed
541

542
(defun flymake-replace-region (buffer beg end rep)
Richard M. Stallman's avatar
Richard M. Stallman committed
543 544 545 546 547
  "Replace text in BUFFER in region (BEG END) with REP."
  (save-excursion
    (delete-region beg end)
    (goto-char beg)
    (insert rep)))
Eli Zaretskii's avatar
Eli Zaretskii committed
548

549
(defun flymake-read-file-to-temp-buffer (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
550 551 552 553 554 555
  "Insert contents of FILE-NAME into newly created temp buffer."
  (let* ((temp-buffer (get-buffer-create (generate-new-buffer-name (concat "flymake:" (file-name-nondirectory file-name))))))
    (save-excursion
      (set-buffer temp-buffer)
      (insert-file-contents file-name))
    temp-buffer))
Eli Zaretskii's avatar
Eli Zaretskii committed
556

557
(defun flymake-copy-buffer-to-temp-buffer (buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
558 559 560 561 562 563 564 565 566 567 568
  "Copy contents of BUFFER into newly created temp buffer."
  (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)
      (insert contents))
    temp-buffer))
Eli Zaretskii's avatar
Eli Zaretskii committed
569

570
(defun flymake-check-include (source-file-name inc-path inc-name include-dirs)
Richard M. Stallman's avatar
Richard M. Stallman committed
571
  "Check if SOURCE-FILE-NAME can be found in include path.
572
Return t if it can be found via include path using INC-PATH and INC-NAME."
Richard M. Stallman's avatar
Richard M. Stallman committed
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
  (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)
	    (setq file-name (concat file-name "/" inc-path)))
	(setq file-name (concat file-name "/" inc-name))
	(when (flymake-same-files source-file-name file-name)
	  (setq found t))
	(setq idx (1+ idx)))
      found)))
Eli Zaretskii's avatar
Eli Zaretskii committed
588

589
(defun flymake-find-buffer-for-file (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
590
  "Check if there exists a buffer visiting FILE-NAME.
591
Return t if so, nil if not."
Richard M. Stallman's avatar
Richard M. Stallman committed
592 593 594
  (let ((buffer-name (get-file-buffer file-name)))
    (if buffer-name
	(get-buffer buffer-name))))
Eli Zaretskii's avatar
Eli Zaretskii committed
595

596
(defun flymake-create-master-file (source-file-name patched-source-file-name get-incl-dirs-f create-temp-f masks include-regexp-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
597
  "Save SOURCE-FILE-NAME with a different name.
598
Find master file, patch and save it."
Richard M. Stallman's avatar
Richard M. Stallman committed
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
  (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)))
	(setq temp-buffer (flymake-read-file-to-temp-buffer master-file-name)))
      (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))
      (setq idx (1+ idx)))
    (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))
	nil))))
Eli Zaretskii's avatar
Eli Zaretskii committed
629

630
(defun flymake-save-buffer-in-file (buffer file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
631 632 633 634 635 636 637 638 639
  (or buffer
      (error "Invalid buffer"))
  (save-excursion
    (save-restriction
      (set-buffer buffer)
      (widen)
      (make-directory (file-name-directory file-name) 1)
      (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
640

641
(defun flymake-save-string-to-file (file-name data)
Richard M. Stallman's avatar
Richard M. Stallman committed
642 643
  "Save string DATA to file FILE-NAME."
  (write-region data nil file-name nil 566))
Eli Zaretskii's avatar
Eli Zaretskii committed
644

645
(defun flymake-read-file-to-string (file-name)
Richard M. Stallman's avatar
Richard M. Stallman committed
646 647 648 649
  "Read contents of file FILE-NAME and return as a string."
  (with-temp-buffer
    (insert-file-contents file-name)
    (buffer-substring (point-min) (point-max))))
Eli Zaretskii's avatar
Eli Zaretskii committed
650

651
(defun flymake-process-filter (process output)
Richard M. Stallman's avatar
Richard M. Stallman committed
652
  "Parse OUTPUT and highlight error lines.
653
It's flymake process filter."
Richard M. Stallman's avatar
Richard M. Stallman committed
654 655
  (let* ((pid               (process-id process))
	 (source-buffer     (get-buffer (flymake-get-source-buffer-name pid))))
Eli Zaretskii's avatar
Eli Zaretskii committed
656

Richard M. Stallman's avatar
Richard M. Stallman committed
657 658 659
    (flymake-log 3 "received %d byte(s) of output from process %d" (length output) pid)
    (when source-buffer
      (flymake-parse-output-and-residual source-buffer output))))
Eli Zaretskii's avatar
Eli Zaretskii committed
660

661
(defun flymake-process-sentinel (process event)
Richard M. Stallman's avatar
Richard M. Stallman committed
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
  "Sentinel for syntax check buffers."
  (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)
		  (flymake-post-syntax-check source-buffer exit-status command)
		  (flymake-set-buffer-is-running source-buffer nil))))
	  (error
	   (let ((err-str (format "Error in process sentinel for buffer %s: %s"
				  source-buffer (error-message-string err))))
	     (flymake-log 0 err-str)
	     (flymake-set-buffer-is-running source-buffer nil)))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
691

692
(defun flymake-post-syntax-check (source-buffer exit-status command)
Richard M. Stallman's avatar
Richard M. Stallman committed
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
  (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))
	    (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
718

719
(defun flymake-parse-output-and-residual (source-buffer output)
Richard M. Stallman's avatar
Richard M. Stallman committed
720 721 722 723 724 725 726 727 728 729 730 731 732
  "Split OUTPUT into lines, merge in residual if necessary."
  (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)
						      source-buffer lines)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
733

734
(defun flymake-parse-residual (source-buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
735 736 737 738 739 740 741 742 743
  "Parse residual if it's non empty."
  (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))))
      (flymake-set-buffer-output-residual source-buffer nil))))
Eli Zaretskii's avatar
Eli Zaretskii committed
744 745

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

Eli Zaretskii's avatar
Eli Zaretskii committed
748
(make-variable-buffer-local 'flymake-err-info)
749 750

(defun flymake-get-buffer-err-info (buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
751
  (flymake-get-buffer-var buffer 'flymake-err-info))
752 753

(defun flymake-set-buffer-err-info (buffer err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
754
  (flymake-set-buffer-var buffer 'flymake-err-info err-info))
755 756

(defun flymake-er-make-er (line-no line-err-info-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
757
  (list line-no line-err-info-list))
758 759

(defun flymake-er-get-line (err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
760
  (nth 0 err-info))
761 762

(defun flymake-er-get-line-err-info-list (err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
763
  (nth 1 err-info))
Eli Zaretskii's avatar
Eli Zaretskii committed
764 765

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

Eli Zaretskii's avatar
Eli Zaretskii committed
768
(make-variable-buffer-local 'flymake-new-err-info)
769 770

(defun flymake-get-buffer-new-err-info (buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
771
  (flymake-get-buffer-var buffer 'flymake-new-err-info))
772 773

(defun flymake-set-buffer-new-err-info (buffer new-err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
774
  (flymake-set-buffer-var buffer 'flymake-new-err-info new-err-info))
Eli Zaretskii's avatar
Eli Zaretskii committed
775 776

;; getters/setters for line-err-info: (file, line, type, text).
777
(defun flymake-ler-make-ler (file line type text &optional full-file)
Richard M. Stallman's avatar
Richard M. Stallman committed
778
  (list file line type text full-file))
779 780

(defun flymake-ler-get-file (line-err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
781
  (nth 0 line-err-info))
782 783

(defun flymake-ler-get-line (line-err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
784
  (nth 1 line-err-info))
785 786

(defun flymake-ler-get-type (line-err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
787
  (nth 2 line-err-info))
788 789

(defun flymake-ler-get-text (line-err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
790
  (nth 3 line-err-info))
791 792

(defun flymake-ler-get-full-file (line-err-info)
Richard M. Stallman's avatar
Richard M. Stallman committed
793
  (nth 4 line-err-info))
794 795

(defun flymake-ler-set-file (line-err-info file)
Richard M. Stallman's avatar
Richard M. Stallman committed
796
  (flymake-ler-make-ler file
Eli Zaretskii's avatar
Eli Zaretskii committed
797 798 799
			(flymake-ler-get-line line-err-info)
			(flymake-ler-get-type line-err-info)
			(flymake-ler-get-text line-err-info)
800 801 802
			(flymake-ler-get-full-file line-err-info)))

(defun flymake-ler-set-full-file (line-err-info full-file)
Richard M. Stallman's avatar
Richard M. Stallman committed
803
  (flymake-ler-make-ler (flymake-ler-get-file line-err-info)
Eli Zaretskii's avatar
Eli Zaretskii committed
804 805 806
			(flymake-ler-get-line line-err-info)
			(flymake-ler-get-type line-err-info)
			(flymake-ler-get-text line-err-info)
807 808 809
			full-file))

(defun flymake-ler-set-line (line-err-info line)
Richard M. Stallman's avatar
Richard M. Stallman committed
810
  (flymake-ler-make-ler (flymake-ler-get-file line-err-info)
Eli Zaretskii's avatar
Eli Zaretskii committed
811 812 813
			line
			(flymake-ler-get-type line-err-info)
			(flymake-ler-get-text line-err-info)
814
			(flymake-ler-get-full-file line-err-info)))
Eli Zaretskii's avatar
Eli Zaretskii committed
815

816
(defun flymake-get-line-err-count (line-err-info-list type)
Richard M. Stallman's avatar
Richard M. Stallman committed
817
  "Return number of errors of specified TYPE.
818
Value of TYPE is eigher e or w."
Richard M. Stallman's avatar
Richard M. Stallman committed
819 820 821
  (let* ((idx        0)
	 (count      (length line-err-info-list))
	 (err-count  0))
Eli Zaretskii's avatar
Eli Zaretskii committed
822

Richard M. Stallman's avatar
Richard M. Stallman committed
823 824 825 826 827
    (while (< idx count)
      (when (equal type (flymake-ler-get-type (nth idx line-err-info-list)))
	(setq err-count (1+ err-count)))
      (setq idx (1+ idx)))
    err-count))
Eli Zaretskii's avatar
Eli Zaretskii committed
828

829
(defun flymake-get-err-count (err-info-list type)
Richard M. Stallman's avatar
Richard M. Stallman committed
830 831 832 833 834 835 836 837
  "Return number of errors of specified TYPE for ERR-INFO-LIST."
  (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)))
      (setq idx (1+ idx)))
    err-count))
838 839

(defun flymake-fix-line-numbers (err-info-list min-line max-line)
Richard M. Stallman's avatar
Richard M. Stallman committed
840
  "Replace line numbers with fixed value.
841 842 843 844
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."
Richard M. Stallman's avatar
Richard M. Stallman committed
845 846 847 848 849 850 851 852 853 854 855 856 857
  (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)
					    (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
858

859
(defun flymake-highlight-err-lines (buffer err-info-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
860 861 862 863 864 865 866 867
  "Highlight error lines in BUFFER using info from ERR-INFO-LIST."
  (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)))
	(setq idx (1+ idx))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
868

869
(defun flymake-overlay-p (ov)
Richard M. Stallman's avatar
Richard M. Stallman committed
870 871
  "Determine whether overlay OV was created by flymake."
  (and (overlayp ov) (overlay-get ov 'flymake-overlay)))
Eli Zaretskii's avatar
Eli Zaretskii committed
872

873
(defun flymake-make-overlay (beg end tooltip-text face mouse-face)
Richard M. Stallman's avatar
Richard M. Stallman committed
874 875 876 877 878 879 880 881 882 883 884
  "Allocate a flymake overlay in range BEG and END."
  (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)
      ov)
    (flymake-log 3 "created an overlay at (%d-%d)" beg end)))
Eli Zaretskii's avatar
Eli Zaretskii committed
885

886
(defun flymake-delete-own-overlays (buffer)
Richard M. Stallman's avatar
Richard M. Stallman committed
887 888 889 890 891 892 893 894 895 896
  "Delete all flymake overlays in BUFFER."
  (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)
	  )
	(setq ov (cdr ov))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
897

898
(defun flymake-region-has-flymake-overlays (beg end)
Richard M. Stallman's avatar
Richard M. Stallman committed
899
  "Check if region specified by BEG and END has overlay.
900
Return t if it has at least one flymake overlay, nil if no overlay."
Richard M. Stallman's avatar
Richard M. Stallman committed
901 902 903 904 905 906
  (let ((ov                  (overlays-in beg end))
	(has-flymake-overlays  nil))
    (while (consp ov)
      (when (flymake-overlay-p (car ov))
	(setq has-flymake-overlays t))
      (setq ov (cdr ov)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
907 908

(defface flymake-errline-face
Richard M. Stallman's avatar
Richard M. Stallman committed
909 910 911 912 913 914
					;+   '((((class color)) (:foreground "OrangeRed" :bold t :underline t))
					;+   '((((class color)) (:underline "OrangeRed"))
  '((((class color)) (:background "LightPink"))
    (t (:bold t)))
  "Face used for marking error lines."
  :group 'flymake)
Eli Zaretskii's avatar
Eli Zaretskii committed
915 916

(defface flymake-warnline-face
Richard M. Stallman's avatar
Richard M. Stallman committed
917 918 919 920
  '((((class color)) (:background "LightBlue2"))
    (t (:bold t)))
  "Face used for marking warning lines."
  :group 'flymake)
Eli Zaretskii's avatar
Eli Zaretskii committed
921

922
(defun flymake-highlight-line (line-no line-err-info-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
923
  "Highlight line LINE-NO in current buffer.
924
Perhaps use text from LINE-ERR-INFO-ILST to enhance highlighting."
Richard M. Stallman's avatar
Richard M. Stallman committed
925 926 927 928 929 930 931 932 933 934 935
  (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]")
      (forward-char))
Eli Zaretskii's avatar
Eli Zaretskii committed
936

Richard M. Stallman's avatar
Richard M. Stallman committed
937
    (setq beg (point))
Eli Zaretskii's avatar
Eli Zaretskii committed
938

Richard M. Stallman's avatar
Richard M. Stallman committed
939 940 941
    (goto-char line-end)
    (while (and (looking-at "[ \t\r\n]") (> (point) 1))
      (backward-char))
Eli Zaretskii's avatar
Eli Zaretskii committed
942

Richard M. Stallman's avatar
Richard M. Stallman committed
943
    (setq end (1+ (point)))
Eli Zaretskii's avatar
Eli Zaretskii committed
944

Richard M. Stallman's avatar
Richard M. Stallman committed
945 946 947
    (when (<= end beg)
      (setq beg line-beg)
      (setq end line-end))
948

Richard M. Stallman's avatar
Richard M. Stallman committed
949 950 951 952
    (when (= end beg)
      (goto-char end)
      (forward-line)
      (setq end (point)))
953

Richard M. Stallman's avatar
Richard M. Stallman committed
954 955 956
    (if (> (flymake-get-line-err-count line-err-info-list "e") 0)
	(setq face 'flymake-errline-face)
      (setq face 'flymake-warnline-face))
957

Richard M. Stallman's avatar
Richard M. Stallman committed
958
    (flymake-make-overlay beg end tooltip-text face nil)))
Eli Zaretskii's avatar
Eli Zaretskii committed
959

960
(defun flymake-parse-err-lines (err-info-list source-buffer lines)
Richard M. Stallman's avatar
Richard M. Stallman committed
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
  "Parse err LINES, store info in ERR-INFO-LIST."
  (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))
	  (setq line-err-info (flymake-ler-set-file line-err-info (file-name-nondirectory real-file-name))))

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

984
(defun flymake-split-output (output)
Richard M. Stallman's avatar
Richard M. Stallman committed
985
  "Split OUTPUT into lines.
986
Return last one as residual if it does not end with newline char. Returns ((lines) residual)."
Richard M. Stallman's avatar
Richard M. Stallman committed
987 988 989 990 991 992 993 994
  (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)))
	(setq lines (butlast lines)))
      (list lines residual))))
Eli Zaretskii's avatar
Eli Zaretskii committed
995

996
(defun flymake-reformat-err-line-patterns-from-compile-el (original-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
997
  "Grab error line patterns from ORIGINAL-LIST in compile.el format.
998
Convert it to flymake internal format."
Richard M. Stallman's avatar
Richard M. Stallman committed
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
  (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))
	   (setq converted-list (cons (list regexp file line col) converted-list)))))
     original-list)
    converted-list))
1016 1017

(eval-when-compile
Richard M. Stallman's avatar
Richard M. Stallman committed
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
  (require 'compile))

(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]*\\(.+\\)"
      2 4 nil 5))
   ;; compilation-error-regexp-alist)
   (flymake-reformat-err-line-patterns-from-compile-el compilation-error-regexp-alist-alist)) 
  "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")

					;(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))
					;)
Eli Zaretskii's avatar
Eli Zaretskii committed
1057

1058
(defun flymake-parse-line (line)
Richard M. Stallman's avatar
Richard M. Stallman committed
1059
  "Parse LINE to see if it is an error of warning.
1060
Return its components if so, nil if no."
Richard M. Stallman's avatar
Richard M. Stallman committed
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
  (let ((raw-file-name nil)
	(line-no 0)
	(err-type "e")
	(err-text nil)
	(count (length flymake-err-line-patterns))
	(idx   0)
	(matched nil))
    (while (and (< idx count) (not matched))
      (when (string-match (car (nth idx flymake-err-line-patterns)) line)
	(let* ((file-idx (nth 1 (nth idx flymake-err-line-patterns)))
	       (line-idx (nth 2 (nth idx flymake-err-line-patterns))))

	  (setq raw-file-name (if file-idx (match-string file-idx line) nil))
	  (setq line-no       (if line-idx (string-to-int (match-string line-idx line)) 0))
	  (setq err-text      (if (> (length (nth idx flymake-err-line-patterns)) 4)
				  (match-string (nth 4 (nth idx flymake-err-line-patterns)) line)
				(flymake-patch-err-text (substring line (match-end 0)))))
	  (or err-text (setq err-text "<no error text>"))
	  (if (and err-text (string-match "^[wW]arning" err-text))
	      (setq err-type "w")
	    )
	  (flymake-log 3 "parse line: file-idx=%s line-idx=%s file=%s line=%s text=%s" file-idx line-idx
		       raw-file-name line-no err-text)
	  (setq matched t)))
      (setq idx (1+ idx)))
    (if matched
	(flymake-ler-make-ler raw-file-name line-no err-type err-text)
      ())))
Eli Zaretskii's avatar
Eli Zaretskii committed
1089

1090
(defun flymake-find-err-info (err-info-list line-no)
Richard M. Stallman's avatar
Richard M. Stallman committed
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
  "Find (line-err-info-list pos) for specified LINE-NO."
  (if err-info-list
      (let* ((line-err-info-list  nil)
	     (pos       0)
	     (count     (length err-info-list)))

	(while (and (< pos count) (< (car (nth pos err-info-list)) line-no))
	  (setq pos (1+ pos)))
	(when (and (< pos count) (equal (car (nth pos err-info-list)) line-no))
	  (setq line-err-info-list (flymake-er-get-line-err-info-list (nth pos err-info-list))))
	(list line-err-info-list pos))
    '(nil 0)))
Eli Zaretskii's avatar
Eli Zaretskii committed
1103

1104
(defun flymake-line-err-info-is-less-or-equal (line-one line-two)
Richard M. Stallman's avatar
Richard M. Stallman committed
1105 1106 1107