The real goal I had, when fiddling with this flymake stuff, was to get a menu of "quick fix" options to pop up when flymake displays the errors. Visual Studio does this if you click ALT-Shift-F10, or something like that.
And, I got it to work, in some basic scenarios.
Here's the user experience:
Step 1: write code with an unresolved type reference - in this case, Stream. Flymake flags the problem, like this:
Step 2: Popup the flymake error menu via (flymake-display-err-menu-for-current-line)
Step 3: Select the menu item, and the quick-fix is automatically applied.
I arranged to provide "quick fix" options for a few special cases:
- error CS0246: The type or namespace 'xxxx' could not be found
- error CS1002: semicolon expected
- error CS0103: The name 'identifier' does not exist in the current context.
The trick was, again, advice. This time on the flymake-make-emacs-menu
fn. That function within flymake prepars the data structure that is passed directly to x-popup-menu
. The advice ("after" advice) parses the error list, looks for the known error codes, and if found, "monkey patches" the popup menu, to insert options for fixing the error.
;; The flymake-make-emacs-menu function prepares the menu for display in
;; x-popup-menu. But the menu from flymake is really just a static list
;; of errors. Clicking on any of the items, does nothing. This advice
;; re-jiggers the menu structure to add dynamic actions into the menu,
;; for some error cases. For example, with an unrecognized type error
;; (CS0246), this fn injects a submenu item that when clicked, will
;; insert a using statement into the top of the file. Other errors are
;; also handled.
;;
;; This won't work generally. It required some changes to flymake.el,
;; so that flymake-goto-next-error would go to the line AND column. The
;; original flymake only goes to the line, not the column. Therefore,
;; quickfixes like inserting a semicolon or a namespace in front of a
;; typename, won't work because the position is off.
;;
(defadvice flymake-make-emacs-menu (after
flymake-csharp-offer-quickfix-menu
()
activate compile)
(let* ((menu ad-return-value)
(title (car menu))
(items (cadr menu))
action
new-items
)
(setq new-items (mapcar
'(lambda (x)
(let ((msg (car x)) missing-type namespace m2 m3)
(cond ((or (string-match "error CS0246:" msg)
(string-match "error CS0103:" msg))
(progn
(string-match "^\\(.+'\\([^']+\\)'[^(]+\\)" msg)
(setq missing-type (substring msg
(match-beginning 2)
(match-end 2)))
;; trim the message to get rid of the (did you forget to ...?)
(setq msg
(substring msg
(match-beginning 1)
(match-end 1)))
(setq namespace (csharp-get-namespace-for-type missing-type))
(if namespace
;; the namespace was found
(progn
(setq m2 (concat "insert using " namespace ";"))
(setq m3 (concat namespace "." missing-type))
(list msg
(list m2 'csharp-insert-using-clause-for-type missing-type)
(list m3 'csharp-insert-fully-qualified-type namespace)
(list "resolve this type reference manually")))
;; couldn't find the namespace; maybe it's just a typo
(list msg
(list "resolve this type reference manually")))))
;; missing semicolon
((string-match "error CS1002:" msg)
(progn
(list msg
(list "insert ; " 'insert ";"))
))
;; all other cases
(t
;; no quick fixes for this error
(list msg
(list "resolve this error manually"))))))
(cdr items)))
;; If there's only one menu item, it sometimes won't display
;; properly. The main error message is hidden, and the submenu
;; items become the menu items. I don't know why. Appending a list
;; of ("" nil) to the end, insures that the menu will display
;; properly.
(setq new-items (append new-items (list (list "" nil))))
;; finally, set the return value
(setq ad-return-value (cons title new-items))
;; (setq ad-return-value (list title
;; (list "item1" (list "choice 1.A" 1) (list "choice 1.B" 2))
;; (list "item2" (list "choice 2.A" 3) (list "choice 2.B" 4))
;; (list "item3")
;; ))
))
The "insert using" fix also depends on a lookup capability, that resolves a short type name, like Stream
to a fully-qualified type name, like System.IO.Stream
. That's a separate issue.
If the user selects the menu item to apply the quick fix, it runs a fn to insert a new "using" clause:
(defun csharp-insert-using-clause (namespace)
"inserts a new using clause, for the given namespace"
(interactive "sInsert using clause; Namespace: ")
(save-excursion
(let ((beginning-of-last-using (re-search-backward "^[ \t]*using [^ ]+;")))
(end-of-line)
(newline)
(insert (concat "using " namespace ";"))
)
)
)
I think this could be expanded to handle quick fixes for other types of errors. But I don't know what those easy-to-fix errors might be. If anyone has any ideas, or wants to help, let me know.