From 4ee1d87a957fd94ba10f34faf8f9886b44db0d3a Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sun, 9 Oct 2022 17:48:13 +0300 Subject: [PATCH] ADDED: Flymake integration in sweeprolog-mode * sweeprolog.el: - sweeprolog-enable-flymake: new user option. - sweeprolog--diagnostics: new buffer-local variable. - sweeprolog--diagnostics-report-fn, sweeprolog--diagnostics-changes-beg, sweeprolog--diagnostics-changes-end: new buffer-local variables. - sweeprolog-defface: add docstrings for generated functions. - sweeprolog--colourise: accumulate diagnostics when sweeprolog-enable-flymake is non-nil. - sweeprolog-colourise-buffer, sweeprolog-colourise-some-terms: report diagnostics when sweeprolog-enable-flymake is non-nil. - sweeprolog-show-diagnostics: new command in sweeprolog-mode buffers. - sweeprolog-mode-map: bind it. - sweeprolog-diagnostic-function: new function. - sweeprolog-mode: use it when sweeprolog-enable-flymake is non-nil. * README.org: Examining diagnostics: new section. * NEWS.org: announce flymake integration. --- NEWS.org | 18 +++++++ README.org | 55 +++++++++++++++++++- sweeprolog.el | 136 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 195 insertions(+), 14 deletions(-) diff --git a/NEWS.org b/NEWS.org index 1239edc..5417c11 100644 --- a/NEWS.org +++ b/NEWS.org @@ -11,6 +11,24 @@ SWI-Prolog in Emacs. For further details, please consult the manual: . +* Version 0.6.0 on 2022-10-10 + +** Added integration with Flymake + +=sweeprolog.el= can now leverage ~flymake~ to highlight and browse +diagnostics in ~sweeprolog-mode~ buffers. + +** New user option ~sweeprolog-enable-flymake~ + +Boolean flag, enabled by default. When customized to nil, +~sweeprolog-mode~ integration with ~flymake~ is disabled. + +** New command ~sweeprolog-show-diagnostics~ + +Wrapper around ~flymake-show-buffer-diagnostics~ for ~sweeprolog-mode~, +bound to ~C-c C-`~. With a prefix argument, calls +~flymake-show-project-diagnostics~ instead. + * Version 0.5.4 on 2022-10-09 ** The manual now has a short description attached to each section diff --git a/README.org b/README.org index 490d887..92959a3 100644 --- a/README.org +++ b/README.org @@ -261,6 +261,7 @@ processing of Prolog query results in Elisp (see =sweeprolog-next-solution=). ** Example - counting solutions for a Prolog predicate in Elisp :PROPERTIES: :CUSTOM_ID: count-permutations +:DESCRIPTION: :END: As an example of using the =sweep= interface for executing Prolog @@ -447,8 +448,10 @@ the buffer. #+FINDEX: sweeprolog-colourise-buffer At any point in a =sweeprolog-mode= buffer, the command =C-c C-c= (or =M-x sweeprolog-colourise-buffer=) can be used to update the cross reference -cache and highlight the buffer accordingly. This may be useful -e.g. after defining a new predicate. +cache and highlight the buffer accordingly. When ~flymake~ integration +is enabled, this command also updates the diagnostics for the current +buffer (see [[*Examining diagnostics][Examining diagnostics]]). This may be useful e.g. after +defining a new predicate. #+VINDEX: sweeprolog-colourise-buffer-on-idle #+VINDEX: sweeprolog-colourise-buffer-max-size @@ -820,6 +823,43 @@ of the frame. To disable the =ElDoc= integration in =sweeprolog-mode= buffers, customize the user option =sweeprolog-enable-eldoc= to nil. +** Examining diagnostics +:PROPERTIES: +:CUSTOM_ID: diagnostics +:DESCRIPTION: Commands for finding errors in Prolog code +:END: + +#+CINDEX: flymake +#+CINDEX: diagnostics +~sweeprolog-mode~ can diagnose problems in Prolog code and report them +to the user by integrating with ~flymake~, a powerful interface for +on-the-fly diagnostics built into Emacs. + +#+FINDEX: sweeprolog-enable-flymake +~flymake~ integration is enabled by default, to disable it customize the +user option ~sweeprolog-enable-flymake~ to nil. + +#+FINDEX: next-error +#+KINDEX: M-g n +#+KINDEX: M-g p +When this integration is enabled, several ~flymake~ commands are +available for listing and jumping between found errors. For a full +description of these commands, see [[info:flymake#Finding diagnostics][Finding diagnostics in the Flymake +manual]]. Additionally, ~sweeprolog-mode~ configures the standard +command ~M-x next-error~ to operate on ~flymake~ diagnostics. This allows +for moving to the next (or previous) error location with the common +~M-g n~ (or ~M-g p~) keybinding. For more information about these +commands, see [[info:emacs#Compilation Mode][Compilation Mode in the Emacs manual]]. + +#+FINDEX: sweeprolog-show-diagnostics +#+KINDEX: C-c C-` +#+KINDEX: C-u C-c C-` +The command ~sweeprolog-show-diagnostics~ shows a list of ~flymake~ +diagnostics for the current buffer. It is bound by default to ~C-c C-`~ +in ~sweeprolog-mode~ buffers with ~flymake~ integration enabled. When +called with a prefix argument (~C-u C-c C-`~), shows a list of +diagnostics for all buffers in the current project. + * The Prolog top-level :PROPERTIES: :CUSTOM_ID: prolog-top-level @@ -1289,24 +1329,35 @@ there some further improvements that we want to pursue: * Indices :PROPERTIES: :CUSTOM_ID: indices +:DESCRIPTION: :END: ** Function index :PROPERTIES: :INDEX: fn :CUSTOM_ID: findex +:DESCRIPTION: :END: ** Variable index :PROPERTIES: :INDEX: vr :CUSTOM_ID: vindex +:DESCRIPTION: +:END: + +** Keystroke index +:PROPERTIES: +:INDEX: ky +:CUSTOM_ID: kindex +:DESCRIPTION: :END: ** Concept index :PROPERTIES: :INDEX: cp :CUSTOM_ID: cindex +:DESCRIPTION: :END: #+html: --> diff --git a/sweeprolog.el b/sweeprolog.el index 970801a..fccf3e6 100644 --- a/sweeprolog.el +++ b/sweeprolog.el @@ -30,6 +30,8 @@ (require 'comint) (require 'xref) (require 'autoinsert) +(require 'eldoc) +(require 'flymake) (defgroup sweeprolog nil "SWI-Prolog Embedded in Emacs." @@ -168,6 +170,12 @@ inserted to the input history in `sweeprolog-top-level-mode' buffers." :type '(repeat string) :group 'sweeprolog) +(defcustom sweeprolog-enable-flymake t + "If non-nil, enable `flymake' suport in `sweeprolog-mode' buffers." + :package-version '((sweeprolog "0.6.0")) + :type 'boolean + :group 'sweeprolog) + (defcustom sweeprolog-enable-eldoc t "If non-nil, enable `eldoc' suport in `sweeprolog-mode' buffers." :package-version '((sweeprolog "0.4.7")) @@ -652,6 +660,7 @@ module name, F is a functor name and N is its arity." ,(concat "Face used to highlight " (downcase doc)) :group 'sweeprolog-faces) (defun ,func () + ,(concat "Return the face used to highlight " (downcase doc)) (pcase sweeprolog-faces-style ('light ',facl) ('dark ',facd) @@ -1217,6 +1226,16 @@ module name, F is a functor name and N is its arity." "Structured comments.") (defvar-local sweeprolog--variable-at-point nil) +(defvar-local sweeprolog--diagnostics nil) +(defvar-local sweeprolog--diagnostics-report-fn nil) +(defvar-local sweeprolog--diagnostics-changes-beg nil) +(defvar-local sweeprolog--diagnostics-changes-end nil) + +(defun sweeprolog-diagnostic-function (report-fn &rest rest) + (setq sweeprolog--diagnostics nil + sweeprolog--diagnostics-report-fn report-fn + sweeprolog--diagnostics-changes-beg (plist-get rest :changes-start) + sweeprolog--diagnostics-changes-end (plist-get rest :changes-end))) (defun sweeprolog--colour-term-to-faces (beg end arg) (pcase arg @@ -1226,7 +1245,12 @@ module name, F is a functor name and N is its arity." (`("comment" . ,_) (list (list beg end nil) (list beg end (sweeprolog-comment-face)))) - (`("head" "unreferenced" . ,_) + (`("head" "unreferenced" ,f ,a) + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end + :note (format "Unreferenced definition for %s/%s" f a)) + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-head-unreferenced-face)))) (`("head" "meta" . ,_) (list (list beg end (sweeprolog-head-meta-face)))) @@ -1256,7 +1280,12 @@ module name, F is a functor name and N is its arity." (list (list beg end (sweeprolog-meta-face)))) (`("goal" "built_in" . ,_) (list (list beg end (sweeprolog-built-in-face)))) - (`("goal" "undefined" . ,_) + (`("goal" "undefined" ,f ,a) + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end + :warning (format "Undefined predicate %s/%s" f a)) + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-undefined-face)))) (`("goal" "global" . ,_) (list (list beg end (sweeprolog-global-face)))) @@ -1276,14 +1305,44 @@ module name, F is a functor name and N is its arity." (list (list beg end (sweeprolog-global-face)))) (`("goal",(rx "local(") . ,_) (list (list beg end (sweeprolog-local-face)))) - (`("syntax_error" ,_message ,eb ,ee) + ("instantiation_error" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "Instantiation error") + sweeprolog--diagnostics)) + (list (list beg end (sweeprolog-instantiation-error-face)))) + ("type_error" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "Type error") + sweeprolog--diagnostics)) + (list (list beg end (sweeprolog-type-error-face)))) + (`("syntax_error" ,message ,eb ,ee) + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :error message) + sweeprolog--diagnostics)) (list (list eb ee nil) (list eb ee (sweeprolog-around-syntax-error-face)) (list beg end (sweeprolog-syntax-error-face)))) ("unused_import" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :note "Unused import") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-unused-import-face)))) ("undefined_import" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "Undefined import") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-undefined-import-face)))) + ("error" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "Unspecified error") + sweeprolog--diagnostics)) + (list (list beg end (sweeprolog-error-face)))) ("html_attribute" (list (list beg end (sweeprolog-html-attribute-face)))) ("html" @@ -1299,6 +1358,10 @@ module name, F is a functor name and N is its arity." ("flag_name" (list (list beg end (sweeprolog-flag-name-face)))) ("no_flag_name" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "No such flag") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-flag-name-face)))) ("ext_quant" (list (list beg end (sweeprolog-ext-quant-face)))) @@ -1309,10 +1372,18 @@ module name, F is a functor name and N is its arity." ("int" (list (list beg end (sweeprolog-int-face)))) ("singleton" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :note "Singleton variable") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-singleton-face)))) ("option_name" (list (list beg end (sweeprolog-option-name-face)))) ("no_option_name" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "No such option") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-no-option-name-face)))) ("control" (list (list beg end (sweeprolog-control-face)))) @@ -1365,7 +1436,8 @@ module name, F is a functor name and N is its arity." (dolist (prop '(font-lock-face face)) (let ((new-prop (get-text-property pos prop))) (when new-prop - (setq res (cons (list (+ beg (1- pos)) (1- (+ beg next)) new-prop) res))))) + (push (list (+ beg (1- pos)) (1- (+ beg next)) new-prop) + res)))) (setq pos next))) (set-buffer-modified-p nil) res)) @@ -1383,8 +1455,16 @@ module name, F is a functor name and N is its arity." ("file" (list (list beg end (sweeprolog-file-face)))) ("file_no_depend" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :note "Unused dependency") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-file-no-depend-face)))) ("nofile" + (when sweeprolog-enable-flymake + (push + (flymake-make-diagnostic (current-buffer) beg end :warning "No such file") + sweeprolog--diagnostics)) (list (list beg end (sweeprolog-no-file-face)))) ("op_type" (list (list beg end (sweeprolog-op-type-face)))) @@ -1419,7 +1499,10 @@ module name, F is a functor name and N is its arity." (remove-list-of-text-properties b e '(font-lock-face)))))))) (defun sweeprolog-colourise-buffer (&optional buffer) + "Update cross-reference data and semantic highlighting in BUFFER." (interactive) + (when sweeprolog-enable-flymake + (flymake-start)) (with-current-buffer (or buffer (current-buffer)) (let* ((beg (point-min)) (end (point-max)) @@ -1427,20 +1510,25 @@ module name, F is a functor name and N is its arity." (with-silent-modifications (font-lock-unfontify-region beg end)) (sweeprolog-open-query "user" - "sweep" - "sweep_colourise_buffer" - (cons contents (buffer-file-name))) + "sweep" + "sweep_colourise_buffer" + (cons contents (buffer-file-name))) (let ((sol (sweeprolog-next-solution))) (sweeprolog-close-query) + (when sweeprolog--diagnostics-report-fn + (funcall sweeprolog--diagnostics-report-fn sweeprolog--diagnostics) + (setq sweeprolog--diagnostics-report-fn nil)) sol)))) (defun sweeprolog-colourise-some-terms (beg0 end0 &optional _verbose) + (when sweeprolog-enable-flymake + (flymake-start)) (let* ((beg (save-mark-and-excursion - (goto-char beg0) + (goto-char (min beg0 (or sweeprolog--diagnostics-changes-beg beg0))) (sweeprolog-beginning-of-top-term) (max (1- (point)) (point-min)))) (end (save-mark-and-excursion - (goto-char end0) + (goto-char (max end0 (or sweeprolog--diagnostics-changes-end end0))) (sweeprolog-end-of-top-term) (point))) (contents (buffer-substring-no-properties beg end))) @@ -1454,6 +1542,11 @@ module name, F is a functor name and N is its arity." beg)) (let ((sol (sweeprolog-next-solution))) (sweeprolog-close-query) + (when sweeprolog--diagnostics-report-fn + (funcall sweeprolog--diagnostics-report-fn + sweeprolog--diagnostics + :region (cons beg end)) + (setq sweeprolog--diagnostics-report-fn nil)) (when (sweeprolog-true-p sol) `(jit-lock-bounds ,beg . ,end))))) @@ -1718,7 +1811,7 @@ Interactively, a prefix arg means to prompt for BUFFER." (defun sweeprolog-end-of-top-term () (unless (eobp) (while (and (nth 8 (syntax-ppss)) (not (eobp))) - (forward-char)) + (forward-char)) (or (re-search-forward (rx "." (or white "\n")) nil t) (goto-char (point-max))) (while (and (or (nth 8 (syntax-ppss)) @@ -1731,6 +1824,18 @@ Interactively, a prefix arg means to prompt for BUFFER." (or (re-search-forward (rx "." (or white "\n")) nil t) (goto-char (point-max)))))) +(defun sweeprolog-show-diagnostics (&optional proj) + "Show diagnostics for the current project, or buffer if PROJ is nil. + +Interactively, PROJ is the prefix argument." + (interactive "P" sweeprolog-mode) + (if (and sweeprolog-enable-flymake + flymake-mode) + (if proj + (flymake-show-project-diagnostics) + (flymake-show-buffer-diagnostics)) + (user-error "Flymake is not active in the current buffer"))) + (defvar sweeprolog-mode-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?_ "_" table) @@ -1755,6 +1860,10 @@ Interactively, a prefix arg means to prompt for BUFFER." (define-key map (kbd "C-c C-t") #'sweeprolog-top-level) (define-key map (kbd "C-c C-o") #'sweeprolog-find-file-at-point) (define-key map (kbd "C-c C-d") #'sweeprolog-document-predicate-at-point) + (define-key map (kbd "C-c C-`") + (if (fboundp 'flymake-show-buffer-diagnostics) ;; Flymake 1.2.1+ + #'sweeprolog-show-diagnostics + #'flymake-show-diagnostics-buffer)) (define-key map (kbd "C-M-^") #'kill-backward-up-list) map) "Keymap for `sweeprolog-mode'.") @@ -2326,8 +2435,7 @@ predicate definition at or directly above POINT." (2 "Second") (3 "Third") (_ (concat (number-to-string cur) "th"))))) - (setq arguments (cons (read-string (concat num " argument: ")) - arguments))) + (push (read-string (concat num " argument: ")) arguments)) (setq cur (1+ cur))) (setq arguments (reverse arguments)) (let ((det (cadr (read-multiple-choice "Determinism: " @@ -2541,6 +2649,10 @@ if-then-else constructs in SWI-Prolog." (when sweeprolog-enable-eldoc (setq-local eldoc-documentation-strategy #'eldoc-documentation-default) (add-hook 'eldoc-documentation-functions #'sweeprolog-predicate-modes-doc nil t)) + (when sweeprolog-enable-flymake + (add-hook 'flymake-diagnostic-functions #'sweeprolog-diagnostic-function nil t) + (flymake-mode) + (setq-local next-error-function #'flymake-goto-next-error)) (when (and (boundp 'cycle-spacing-actions) (consp cycle-spacing-actions) sweeprolog-enable-cycle-spacing -- 2.39.2