Subject: flymake.el - enhancement request - flymake-goto-next-error should go to the column, if possible
From: "D Chiesa" <[email-masked]>
To: <[email-masked]>
Date: Fri, 9 Apr 2010 01:44:59 -0400
List-Id: <debbugs-submit.debbugs.gnu.org>

I use emacs v22.2.1 on Windows.

Flymake relies on compilation error patterns, similar to 
compilation-error-regexp-alist , to detect errors and warnings.
But flymake discards column information for any errors, and as a result, 
the functions flymake-goto-next-error and flymake-goto-prev-error do not 
position the cursor on the error column.

This discards useful information, and also makes it more difficult to add 
enhancements to flymake - such as the ability to offer a menu of "quick 
fixes" that can be applied on the spot, through 
flymake-display-err-menu-for-current-line .

The fix is to:

1- modify the flymake-ler struct, like so:

(defstruct (flymake-ler
            (:constructor nil)
            ;; cheeso = 2010 apr 8 - add col-no field
            (:constructor flymake-ler-make-ler (file line type text 
&optional full-file col-no)))
  file line type text full-file col-no)

2- modify the flymake-ler functions, accordingly

(defun flymake-ler-set-file (line-err-info file)
  (flymake-ler-make-ler file
                        (flymake-ler-line line-err-info)
                        (flymake-ler-type line-err-info)
                        (flymake-ler-text line-err-info)
                        (flymake-ler-full-file line-err-info)
                        ;; cheeso - 2010 apr 8
                        (flymake-ler-col-no line-err-info)
                        ))

(defun flymake-ler-set-full-file (line-err-info full-file)
  (flymake-ler-make-ler (flymake-ler-file line-err-info)
                        (flymake-ler-line line-err-info)
                        (flymake-ler-type line-err-info)
                        (flymake-ler-text line-err-info)
                        full-file
                        ;; cheeso - 2010 apr 8
                        (flymake-ler-col-no line-err-info)
                        ))

(defun flymake-ler-set-line (line-err-info line)
  (flymake-ler-make-ler (flymake-ler-file line-err-info)
                        line
                        (flymake-ler-type line-err-info)
                        (flymake-ler-text line-err-info)
                        (flymake-ler-full-file line-err-info)
                        ;; cheeso - 2010 apr 8
                        (flymake-ler-col-no line-err-info)
                        ))

3- modify   flymake-parse-line  to capture the column number, and store it 
in the flymake-ler struct that is created. (same as line-no, but use index 
3, instead of 2)

(defun flymake-parse-line (line)
  "Parse LINE to see if it is an error or warning.
Return its components if so, nil otherwise."
  (let ((raw-file-name nil)
        (line-no 0)
        (col-no 0)
        (err-type "e")
        (err-text nil)
        (patterns flymake-err-line-patterns)
        (matched nil))
    (while (and patterns (not matched))
      (when (string-match (car (car patterns)) line)
        (let* ((file-idx (nth 1 (car patterns)))
               (line-idx (nth 2 (car patterns)))
               (col-idx (nth 3 (car patterns)))
               )
          (setq raw-file-name (if file-idx (match-string file-idx line) 
nil))
          (setq line-no       (if line-idx (string-to-number (match-string 
line-idx line)) 0))
          (setq col-no        (if col-idx  (string-to-number (match-string 
col-idx line)) 0))
          (setq err-text      (if (> (length (car patterns)) 4)
                                  (match-string (nth 4 (car 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 patterns (cdr patterns)))
    (if matched
        ;; cheeso
        ;;(flymake-ler-make-ler raw-file-name line-no err-type err-text)
        (flymake-ler-make-ler raw-file-name line-no err-type err-text nil 
col-no)
      ())))

4 - introduce new fn flymake-er-get-line-col , which returns (LINE COL) for 
the error.

(defun flymake-er-get-line-col (err-info)
  (list (nth 0 err-info)
        (flymake-ler-col-no (car (nth 1 err-info))) ;; xxxx
        ))

5- transform all fns that get line number info, to get line+col info:

(defun flymake-get-first-err-line-col (err-info-list)
  "Return first line with error."
  (when err-info-list
    (flymake-er-get-line-col (car err-info-list))))

(defun flymake-get-last-err-line-col (err-info-list)
  "Return last line with error."
  (when err-info-list
    (flymake-er-get-line-col (nth (1- (length err-info-list)) 
err-info-list))))

(defun flymake-get-next-err-line-col (err-info-list line-no)
  "Return next line with error."
  (when err-info-list
    (let* ((count  (length err-info-list))
           (idx    0))
      (while (and (< idx count) (>= line-no (flymake-er-get-line (nth idx 
err-info-list))))
        (setq idx (1+ idx)))
      (if (< idx count)
          (flymake-er-get-line-col (nth idx err-info-list))))))

(defun flymake-get-prev-err-line-col (err-info-list line-no)
  "Return previous line with error."
  (when err-info-list
    (let* ((count (length err-info-list)))
      (while (and (> count 0) (<= line-no (flymake-er-get-line (nth (1- 
count) err-info-list))))
        (setq count (1- count)))
      (if (> count 0)
          (flymake-er-get-line-col (nth (1- count) err-info-list))))))

6- modify flymake-goto-next-error to use the column information

(defun flymake-goto-next-error ()
  "Go to next error in error ring. Return (LINE COL), or nil if there are no 
errors."
  (interactive)
  (let* ((line-col (flymake-get-next-err-line-col flymake-err-info 
(flymake-current-line-no)))
         (line-no (car line-col))
         (col-no  (cadr line-col))
        )
    (when (not line-no)
      (setq line-col (flymake-get-first-err-line-col flymake-err-info))
      (setq line-no (car line-col)
            col-no  (cadr line-col))
      (flymake-log 1 "passed end of file"))
    (if line-no
        (flymake-goto-line line-col)
      (flymake-log 1 "no errors in current buffer"))))

7- same for flymake-goto-prev-error

(defun flymake-goto-prev-error ()
  "Go to previous error in err ring."
  (interactive)
  (let* ((line-col (flymake-get-prev-err-line-col flymake-err-info 
(flymake-current-line-no)))
         (line-no (car line-col))
         (col-no  (cadr line-col))
        )
    (when (not line-no)
        (setq line-col (flymake-get-last-err-line-col flymake-err-info))
        (setq line-no (car line-col)
              col-no  (cadr line-col))
        (flymake-log 1 "passed beginning of file"))
    (if line-no
        (flymake-goto-line line-col)
      (flymake-log 1 "no errors in current buffer"))))

8- modify flymake-goto-line to accept a (LINE COL) list, and act accordingly

(defun flymake-goto-line (line-col)
  "LINE-COL contains (LINE-NO COL-NO). Go to line LINE-NO, then skip to the 
COL-NO column."
  (let ((line-no (car line-col))
        (col-no (cadr line-col))
        )
    (goto-line line-no)
    (when col-no
      (while (> col-no 1)
        (setq col-no (1- col-no))
        (forward-char))))
  line-col)

-Dino Chiesa