I'm using Flymake on C# code, emacs v22.2.1 on Windows.
The Flymake stuff has been working well for me. For those who don't know, you can read an overview of flymake, but the quick story is that flymake repeatedly builds the source file you are currently working on in the background, for the purpose of doing syntax checking. It then highlights the compiler warnings and erros in the current buffer.
Flymake didn't work for C# initially, but I "monkey-patched it" and it works nicely now. If you edit C# in emacs, I highly recommend using flymake.
The only problem I have is with the UI. Flymake highlights the errors and warnings nicely, and then inserts "overlays" with tooltips containing the full error or warning text. If I hover the mouse pointer over the highlighted line in code, the overlay tooltip pops up.
But as you can see, the overlay tooltip is clipped, and it doesn't display correctly.
Flymake seems to be doing the right thing, it's the overlay part that seems broken., and overlay seems to do the right thing. It's the tooltip that is displayed incorrectly.
Do overlays tooltips work correctly in emacs for Windows?
Where do I look to fix this?
After some research, I found that the effect is demonstrable with (tooltip-show really-long-string)
It has nothing to do w开发者_高级运维ith overlays, or flymake.
I solved this with a defadvice on tooltip-show.
;; Reforms a single-line string ARG to a multi-line string with a max
;; of LIMIT chars on a line.
;;
;; This is intended to solve a problem with the display of tooltip text
;; in emacs on Win32 - which is that the tooltip is extended to be very very
;; long, and the final line is clipped.
;;
;; The solution is to split the text into multiple lines, and to add a
;; trailing newline to trick the tooltip logic into doing the right thing.
;;
(defun cheeso-reform-string (limit arg)
(let ((orig arg) (modified "") (curline "") word
(words (split-string arg " ")))
(while words
(progn
(setq curline "")
(while (and words (< (length curline) limit))
(progn
(setq word (car words))
(setq words (cdr words))
(setq curline (concat curline " " word))))
(setq modified (concat modified curline "\n"))))
(setq modified (concat modified " \n")))
)
(defadvice tooltip-show (before
flymake-csharp-fixup-tooltip
(arg &optional use-echo-area)
activate compile)
(progn
(if (and (not use-echo-area)
(eq major-mode 'csharp-mode))
(let ((orig (ad-get-arg 0)))
(ad-set-arg 0 (cheeso-reform-string 72 orig))
))))
result:
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 prepares 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.
精彩评论