;;; error-tip.el --- showing error library by popup.el -*- lexical-binding: t; -*- ;; Copyright (C) 2014 by Yuta Yamada ;; Author: Yuta Yamada ;; URL: https://github.com/yuutayamada/flycheck-tip ;; Version: 0.5.0 ;; Package-Requires: ((emacs "24.1") (popup "0.5.0")) ;;; License: ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;;; Code: (require 'cl-lib) (require 'popup) (require 'notifications) ; this introduced from Emacs 24 (defvar error-tip-notify-keep-messages nil "If the value is non-nil, keep error messages to notification area. This feature only activates when you leave from popup's message.") (defvar error-tip-notify-last-notification nil "Last notification id.") (defvar error-tip-notify-timeout (* 60 1000) "Value for time out. The default value is 1 minute.") (defvar error-tip-notify-parametors '(:title "flycheck-tip" :category "im.error") "Parameters for ‘error-tip-notify’. You can add ‘notifications-notify’s parametors without :body, :replaces-id and :timeout. Example: (setq error-tip-notify-parametors (append error-tip-notify-parametors '(:app-icon \"/path/to/icon-file\")))") ;; INTERNAL VARIABLE (defvar error-tip-popup-object nil) (defvar error-tip-timer-object nil) (defvar error-tip-current-errors nil) (defvar error-tip-timer-delay 0.3 "Whether how much delay showing error popup. If you set nil to this variable, then do not use delay timer.") (defvar error-tip-newline-character nil "Use this variable if you want change specific characters to turn to newlines.") (defvar error-tip-state nil) (defun error-tip-cycle (errors &optional reverse) (error-tip-delete-popup) (setq error-tip-state nil) (when errors (let* ((next (assoc-default :next errors)) (previous (assoc-default :previous errors)) (cur-line (assoc-default :current-line errors)) (jump (lambda (errs) ;; Set errors forcefully at EOB (when (and (eobp) (eq (point) (point-at-bol))) (setq errs previous)) (goto-char (point-min)) (unless (line-move (1- (error-tip-get (car errs) 'line)) t) (push (cons 'eob (line-number-at-pos)) error-tip-state)) (setq error-tip-current-errors errs) (if (null error-tip-timer-delay) (error-tip-popup-error-message (error-tip-get-errors)) (error-tip-cancel-timer) (error-tip-register-timer)))) (target (if (not reverse) (or next previous cur-line) (reverse (or previous next cur-line))))) (funcall jump target)))) (defun error-tip-get (err element) (when (fboundp 'flycheck-tip--get) (flycheck-tip--get element err))) (defun error-tip-collect-current-file-errors (errors) "Collect errors from ERRORS." (cl-loop with c-line = (line-number-at-pos (point)) for err in errors for err-line = (error-tip-get err 'line) if (and buffer-file-truename ; whether file or buffer (not (equal (expand-file-name buffer-file-truename) (error-tip-get err 'file)))) do '() ; skip else if (< c-line err-line) collect err into next else if (> c-line err-line) collect err into previous else if (= c-line err-line) collect err into current-line finally return (when (or next previous current-line) (list (cons :next next) (cons :previous previous) (cons :current-line current-line))))) (defun error-tip-popup-error-message (errors &optional point) "Popup error message(s) from ERRORS. If there are multiple errors on current line, all current line's errors are appeared. The POINT arg is a point to show up error(s)." (setq error-tip-popup-object (popup-tip (error-tip-format errors) :nowait t :point (let ((p (or point (error-tip-get-point)))) (if (and (eobp) (eq (point) (point-at-bol))) (1- p) p)))) (add-hook 'pre-command-hook 'error-tip-delete-popup)) (defun error-tip-get-point () "Return point where the popup message emerges." (1+ (point-at-bol))) (defun error-tip-format (errors) "Format ERRORS." (let ((messages (format "*%s" (mapconcat 'identity errors "\n*")))) (if error-tip-newline-character (replace-regexp-in-string error-tip-newline-character "\n" messages) messages))) (defun error-tip-get-errors () "Get errors." (cl-loop with current-line = (line-number-at-pos (point)) for error in error-tip-current-errors for e-line = (error-tip-get error 'line) for e-str = (error-tip-get error 'message) if (or (equal current-line e-line) (and (equal 1 current-line) (equal 0 e-line))) collect e-str into result else if (or (and (< (- 1 current-line) e-line) (> (+ 1 current-line) e-line)) ;; #12 (let ((line-eob (assoc-default 'eob error-tip-state))) (and line-eob (<= line-eob e-line)))) collect e-str into fallback finally return (or result fallback))) (defun error-tip-delete-popup () "Delete popup object." (condition-case err (when (popup-live-p error-tip-popup-object) (popup-delete error-tip-popup-object) (when error-tip-notify-keep-messages (error-tip-notify))) (error err)) (remove-hook 'pre-command-hook 'error-tip-delete-popup)) (defun error-tip-register-timer () "Register timer that show error message." (setq error-tip-timer-object (run-with-timer error-tip-timer-delay nil (lambda () (error-tip-popup-error-message (error-tip-get-errors)))))) (defun error-tip-cancel-timer () "Cancel `error-tip-timer-object'." (when (timerp error-tip-timer-object) (cancel-timer error-tip-timer-object))) ;;;###autoload (defun error-tip-error-p () "Return non-nil if error is occurred in current buffer. This function can catch error against flycheck, and flymake." (cond ((bound-and-true-p flycheck-current-errors) 'flycheck) ((or (bound-and-true-p flymake-err-info) (and (fboundp 'flymake-diagnostics) (flymake-diagnostics))) 'flymake) (t nil))) ;;;###autoload (defun error-tip-cycle-dwim (&optional reverse) "Showing error function. This function switches proper error showing function by context. (whether flycheck or flymake) The REVERSE option jumps by inverse if the value is non-nil." (interactive) (funcall (cl-case (error-tip-error-p) (flycheck 'flycheck-tip-cycle) (flymake 'flymake-tip-cycle)) reverse)) ;;;###autoload (defun error-tip-cycle-dwim-reverse () "Same as ‘error-tip-cycle-dwim’, but it jumps to inverse direction." (interactive) (error-tip-cycle-dwim t)) ;; Show errors by using notifications.el(D-Bus) (defun error-tip-notify () "Keep ERROR-MESSAGES on notification area. See also ‘error-tip-notify-keep-messages’" (setq error-tip-notify-last-notification (apply `((lambda () (notifications-notify ,@(and error-tip-notify-parametors) :body ,(format "%s" (error-tip-format (if (cl-struct-p (car error-tip-current-errors)) (error-tip-get-errors) error-tip-current-errors))) :replaces-id error-tip-notify-last-notification :timeout error-tip-notify-timeout)))))) ;; Manual test: ;; (defun error-tip--test-toggle-flymake-and-flycheck () ;; (interactive) ;; (if flymake-mode ;; (flymake-mode 0) ;; (flymake-mode 1)) ;; (if flycheck-mode ;; (flycheck-mode 0) ;; (flycheck-mode 1) )) ;; (error-tip--test-toggle-flymake-and-flycheck) (provide 'error-tip) ;; Local Variables: ;; coding: utf-8 ;; mode: emacs-lisp ;; End: ;;; error-tip.el ends here