From a349626558b64ac4928ef71d6ed9a2a37f92575c Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sat, 13 Jul 2024 16:22:12 +0200 Subject: [PATCH] New command 'flymake-go-to-diagnostic' --- lisp/progmodes/flymake.el | 130 ++++++++++++++++++++++++++++++++++++++ lisp/progmodes/project.el | 14 ++++ 2 files changed, 144 insertions(+) diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index 3321ae243a0..78c55716609 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -902,6 +902,136 @@ Return to original margin width if ORIG-WIDTH is non-nil." (message "No fix available for this diagnostic"))) (user-error "No diagnostic at this position"))) +(defun flymake-read-diagnostic (prompt &optional project) + "Prompt with PROMPT for a Flymake diagnostic in the current buffer. + +If optional argument PROJECT is non-nil, prompt for diagnostics across +that project instead." + (let* ((buffer (current-buffer)) + tab cands ind + (cands-fn + (lambda () + (setq tab (make-hash-table) cands nil ind 0) + (dolist (d (if project + (flymake--project-diagnostics project) + (flymake-diagnostics))) + (let ((cand (propertize (format "%-4d:%s" (cl-incf ind) (flymake--diag-text d)) + 'diagnostic d))) + (push cand cands) + (puthash ind d tab))) + (setq cands (nreverse cands)))) + (text + (minibuffer-with-setup-hook + (lambda () + (setq minibuffer-action + (cons + (lambda (c) + (save-selected-window + (flymake-go-to-diagnostic + (gethash (string-to-number c) tab)))) + "goto") + minibuffer-alternative-action + (cons + (lambda (c) + (let ((d (gethash (string-to-number c) tab))) + (save-selected-window + (flymake-go-to-diagnostic d) + (if (not (flymake--fix-diagnostic d)) + (user-error "No fix available for this diagnostic") + (flymake-start) + (flymake--update-diagnostics-listings (current-buffer)) + (with-current-buffer buffer (funcall cands-fn)))))) + "fix"))) + (funcall cands-fn) + (completing-read + prompt + (completion-table-with-metadata + (completion-table-dynamic (lambda (_) cands)) + `((category . flymake-diagnostic) + (group-function + . ,(lambda (string &optional transform) + (cond + (transform + (substring string (1+ (string-search ":" string)))) + (project + (let ((bof (flymake--diag-locus + (gethash (string-to-number string) tab)))) + (if (bufferp bof) (concat "Buffer "(buffer-name bof)) + (concat "File " (file-relative-name + bof (project-root project))))))))) + (affixation-function + . ,(lambda (cs) + (mapcar + (lambda (cand) + (let ((diag (gethash (string-to-number cand) tab))) + (list cand + (format "%s%s " + (if (flymake--diag-fix-function diag) + (propertize "*" 'help-echo "Fix available") + " ") + (cl-case (get (flymake--diag-type diag) 'flymake-category) + (flymake-note (propertize "n" 'face 'flymake-note-echo)) + (flymake-warning (propertize "w" 'face 'flymake-warning-echo)) + (flymake-error (propertize "e" 'face 'flymake-error-echo)) + (otherwise " "))) + ""))) + cs))) + (narrow-completions-function + . ((?f "fix" "Fix available" + ,(lambda () + `(,(lambda (c) + (flymake--diag-fix-function (get-text-property 0 'diagnostic c))) + . "has fix"))) + (?n "note" "Notes" + ,(lambda () + `(,(lambda (c) + (eq (get (flymake--diag-type (get-text-property 0 'diagnostic c)) + 'flymake-category) + 'flymake-note)) + . "type=note"))) + (?w "warning" "Warnings" + ,(lambda () + `(,(lambda (c) + (eq (get (flymake--diag-type (get-text-property 0 'diagnostic c)) + 'flymake-category) + 'flymake-warning)) + . "type=warning"))) + (?e "error" "Errors" + ,(lambda () + `(,(lambda (c) + (eq (get (flymake--diag-type (get-text-property 0 'diagnostic c)) + 'flymake-category) + 'flymake-error)) + . "type=error"))))))) + nil t nil t)))) + (gethash (string-to-number text) tab))) + +(defun flymake-go-to-diagnostic (diag) + "Go to Flymake diagnostic DIAG. + +Interactively, prompt for DIAG with completion. By default, completion +candidates are diagnostics for the current buffer; with prefix argument, +completion candidates are instead all known diagnostics in the current +project. + +During minibuffer input, you can use the minibuffer action to show +candidate diagnostics without exiting the minibuffer. The alternative +minibuffer action lets you fix candidate diagnostics that have available +fix suggestions." + (interactive + (list (save-excursion (flymake-read-diagnostic "Go to diagnostic: " + (when current-prefix-arg + (project-current)))))) + (let* ((bof (flymake--diag-locus diag)) + (buf (if (bufferp bof) bof (find-file-noselect bof))) + (beg (flymake--diag-beg diag))) + (push-mark) + (pop-to-buffer buf) + (goto-char + (if (number-or-marker-p beg) beg + (car (flymake-diag-region (current-buffer) (car beg) (cdr beg))))) + (pulse-momentary-highlight-one-line))) + (cl-defun flymake--highlight-line (diagnostic &optional foreign) "Attempt to overlay DIAGNOSTIC in current buffer. diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 4dd83930214..8369f6f5120 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -880,6 +880,7 @@ DIRS must contain directory names." (define-key map "x" 'project-execute-extended-command) (define-key map "o" 'project-any-command) (define-key map "\C-b" 'project-list-buffers) + (define-key map "`" 'project-go-to-diagnostic) map) "Keymap for project commands.") @@ -1615,6 +1616,19 @@ If FILES-ONLY is non-nil, only show the file-visiting buffers." (member (current-buffer) (project-buffers ',project))))))) +(declare-function flymake-read-diagnostic "flymake") +(declare-function flymake-go-to-diagnostic "flymake") + +;;;###autoload +(defun project-go-to-diagnostic (diag) + "Go to Flymake diagnostic DIAG." + (interactive + (list (save-excursion + (require 'flymake) + (flymake-read-diagnostic "Project diagnostic: " (project-current t))))) + (require 'flymake) + (flymake-go-to-diagnostic diag)) + (defcustom project-kill-buffer-conditions '(buffer-file-name ; All file-visiting buffers are included. ;; Most of temp and logging buffers (aside from hidden ones): -- 2.39.2