;;; lsp-dart-test-output.el --- Test output features and decorations -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2020 Eric Dallo ;; ;; 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: ;; ;; Test output features and decorations ;; ;;; Code: (require 'rx) (require 'lsp-dart-protocol) (require 'lsp-dart-utils) (defcustom lsp-dart-test-pop-to-buffer-on-run 'display-only "Controls whether to pop to the tests buffer on run. When set to nil the buffer will only be created, and not displayed. When set to `display-only' the buffer will be displayed, but it will not become focused, otherwise the buffer is displayed and focused." :group 'lsp-dart :type '(choice (const :tag "Create the buffer, but don't display it" nil) (const :tag "Create and display the buffer, but don't focus it" display-only) (const :tag "Create, display, and focus the buffer" t))) ;;; Internal (defconst lsp-dart-test-output--passed-icon "★") (defconst lsp-dart-test-output--success-icon "✔") (defconst lsp-dart-test-output--skipped-icon "•") (defconst lsp-dart-test-output--hidden-icon "○") (defconst lsp-dart-test-output--error-icon "✖") (defvar lsp-dart-test-output--tests-count 0) (defvar lsp-dart-test-output--tests-passed 0) (defvar lsp-dart-test-output--first-log t) (defconst lsp-dart-test-output--buffer-name "*LSP Dart tests*") (defconst lsp-dart-test-output--exception-re (rx (or (and (zero-or-more any) (or "exception" "EXCEPTION") (zero-or-more any)) "" (and "#" (one-or-more any))))) (defconst lsp-dart-test-output--expected-actual-re (rx (or (and (zero-or-more blank) "Expected:" (zero-or-more any)) (and (zero-or-more blank) "Actual:" (zero-or-more any))))) (defconst lsp-dart-test--font-lock `((,lsp-dart-test-output--exception-re . 'error) (,lsp-dart-test-output--expected-actual-re . 'warning))) (defvar lsp-dart-test--output-font-lock '((lsp-dart-test--font-lock))) (lsp-defun lsp-dart-test-output--get-icon ((&TestDoneNotification :result :skipped :hidden)) "Return the icon for test done notification." (cond (hidden lsp-dart-test-output--hidden-icon) ((and (string= result "success") skipped) lsp-dart-test-output--skipped-icon) ((and (string= result "success") (not skipped)) lsp-dart-test-output--success-icon) (t lsp-dart-test-output--error-icon))) (lsp-defun lsp-dart-test-output--get-face ((&TestDoneNotification :result :skipped :hidden)) "Return the icon for test done notification." (cond (hidden 'font-lock-comment-face) ((and (string= result "success") skipped) 'homoglyph) ((and (string= result "success") (not skipped)) 'success) (t 'error))) (defun lsp-dart-test-output--send (message &rest args) "Send MESSAGE with ARGS to test buffer." (let* ((inhibit-read-only t)) (with-current-buffer (lsp-dart-test-output--get-buffer-create) (save-excursion (goto-char (point-max)) (insert (apply #'format (concat message "\n") args)))))) (defun lsp-dart-test-output--get-buffer-create () "Create a buffer for test display." (let ((buffer (get-buffer-create lsp-dart-test-output--buffer-name))) (with-current-buffer buffer (setq-local default-directory (or (lsp-dart-get-project-root) default-directory)) (unless (derived-mode-p 'lsp-dart-test-output-content-mode) (lsp-dart-test-output-content-mode)) (current-buffer)))) (defun lsp-dart-test-output--show-buffer () "Show test buffer." (let ((test-buffer (lsp-dart-test-output--get-buffer-create)) (inhibit-read-only t)) (with-current-buffer test-buffer (let ((inhibit-read-only t)) (erase-buffer))) (pcase lsp-dart-test-pop-to-buffer-on-run (`display-only (let ((orig-buffer (current-buffer))) (display-buffer test-buffer) (set-buffer orig-buffer))) ((pred identity) (pop-to-buffer test-buffer))))) (defun lsp-dart-test-output--handle-run-started () "Handle test run started." (setq lsp-dart-test-output--first-log t) (lsp-dart-test-output--show-buffer) (lsp-dart-test-output--send "Running tests...\n")) (defun lsp-dart-test-output--handle-all-start (_notification) "Handle all start notification." (setq lsp-dart-test-output--tests-count 0) (setq lsp-dart-test-output--tests-passed 0)) (lsp-defun lsp-dart-test-output--handle-start ((&TestStartNotification :test (&Test :group-i-ds))) (unless (seq-empty-p group-i-ds) (setq lsp-dart-test-output--tests-count (1+ lsp-dart-test-output--tests-count)))) (lsp-defun lsp-dart-test-output--handle-done ((notification &as &TestDoneNotification :result :time :hidden) test-name test-start-time) "Handle test done notification." (when lsp-dart-test-output--first-log (with-current-buffer (lsp-dart-test-output--get-buffer-create) (let ((inhibit-read-only t)) (erase-buffer))) (setq lsp-dart-test-output--first-log nil)) (let ((text (propertize (concat (lsp-dart-test-output--get-icon notification) " " test-name) 'font-lock-face (lsp-dart-test-output--get-face notification)))) (if hidden (lsp-dart-test-output--send "%s" text) (progn (when (string= result "success") (setq lsp-dart-test-output--tests-passed (1+ lsp-dart-test-output--tests-passed))) (let ((formatted-time (propertize (format "(%s ms)" (- time test-start-time)) 'font-lock-face 'font-lock-comment-face))) (lsp-dart-test-output--send "%s %s" text formatted-time)))))) (lsp-defun lsp-dart-test-output--handle-all-done ((&DoneNotification :success)) "Handle all tests done notification." (if success (lsp-dart-test-output--send (propertize (format "\n%s All ran tests passed %s" lsp-dart-test-output--passed-icon lsp-dart-test-output--passed-icon) 'font-lock-face 'success)) (lsp-dart-test-output--send (propertize (format "\n● %s/%s tests passed" lsp-dart-test-output--tests-passed lsp-dart-test-output--tests-count) 'font-lock-face font-lock-warning-face)))) (lsp-defun lsp-dart-test-output--handle-print ((&PrintNotification :message)) "Handle test print notification." (lsp-dart-test-output--send "%s" message)) (lsp-defun lsp-dart-test-output--handle-error ((&ErrorNotification :error :stack-trace)) "Handle test error notification." (lsp-dart-test-output--send "%s" error) (lsp-dart-test-output--send "%s" stack-trace)) (define-derived-mode lsp-dart-test-output-content-mode special-mode lsp-dart-test-output--buffer-name "Major mode for buffer running tests." (setq font-lock-defaults lsp-dart-test--output-font-lock)) (add-hook 'lsp-dart-test-run-started-hook #'lsp-dart-test-output--handle-run-started) (add-hook 'lsp-dart-test-all-start-notification-hook #'lsp-dart-test-output--handle-all-start) (add-hook 'lsp-dart-test-start-notification-hook #'lsp-dart-test-output--handle-start) (add-hook 'lsp-dart-test-done-notification-hook #'lsp-dart-test-output--handle-done) (add-hook 'lsp-dart-test-all-done-notification-hook #'lsp-dart-test-output--handle-all-done) (add-hook 'lsp-dart-test-print-notification-hook #'lsp-dart-test-output--handle-print) (add-hook 'lsp-dart-test-error-notification-hook #'lsp-dart-test-output--handle-error) (provide 'lsp-dart-test-output) ;;; lsp-dart-test-output.el ends here