You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
;;; polymode-debug.el --- Interactive debugging utilities for polymode -*- lexical-binding: t -*-;;;; Copyright (C) 2016-2018 Vitalie Spinu;; Author: Vitalie Spinu;; URL: https://github.com/polymode/polymode;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; This file is *NOT* part of GNU Emacs.;;;; 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, 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Commentary:;;
;;; Code:
(require 'polymode-core)(require 'poly-lock)(require 'trace)
;;; MINOR MODE
(defvar pm--underline-overlay (let ((overlay (make-overlay (point) (point)))) (overlay-put overlay 'face '(:underline (:color "tomato" :style wave))) overlay) "Overlay used in function `pm-debug-mode'.")
(defvar pm--highlight-overlay (let ((overlay (make-overlay (point) (point)))) (overlay-put overlay 'face '(:inverse-video t)) overlay) "Overlay used by `pm-debug-map-over-spans-and-highlight'.")
(defvar pm-debug-minor-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "M-n M-i") #'pm-debug-info-on-current-span) (define-key map (kbd "M-n i") #'pm-debug-info-on-current-span) (define-key map (kbd "M-n M-p") #'pm-debug-relevant-variables) (define-key map (kbd "M-n p") #'pm-debug-relevant-variables) (define-key map (kbd "M-n M-h") #'pm-debug-map-over-spans-and-highlight) (define-key map (kbd "M-n h") #'pm-debug-map-over-spans-and-highlight) (define-key map (kbd "M-n M-t t") #'pm-toggle-tracing) (define-key map (kbd "M-n M-t i") #'pm-debug-toogle-info-message) (define-key map (kbd "M-n M-t f") #'pm-debug-toggle-fontification) (define-key map (kbd "M-n M-t p") #'pm-debug-toggle-post-command) (define-key map (kbd "M-n M-t c") #'pm-debug-toggle-after-change) (define-key map (kbd "M-n M-t a") #'pm-debug-toggle-all) (define-key map (kbd "M-n M-t M-t") #'pm-toggle-tracing) (define-key map (kbd "M-n M-t M-i") #'pm-debug-toogle-info-message) (define-key map (kbd "M-n M-t M-f") #'pm-debug-toggle-fontification) (define-key map (kbd "M-n M-t M-p") #'pm-debug-toggle-post-command) (define-key map (kbd "M-n M-t M-c") #'pm-debug-toggle-after-change) (define-key map (kbd "M-n M-t M-a") #'pm-debug-toggle-all) (define-key map (kbd "M-n M-f s") #'pm-debug-fontify-current-span) (define-key map (kbd "M-n M-f b") #'pm-debug-fontify-current-buffer) (define-key map (kbd "M-n M-f M-t") #'pm-debug-toggle-fontification) (define-key map (kbd "M-n M-f M-s") #'pm-debug-fontify-current-span) (define-key map (kbd "M-n M-f M-b") #'pm-debug-fontify-current-buffer) map))
;;;###autoload(define-minor-mode pm-debug-minor-mode "Turns on/off useful facilities for debugging polymode.
Key bindings:\\{pm-debug-minor-mode-map}"
nil " PMDBG" :group 'polymode (if pm-debug-minor-mode (progn ;; this is global hook. No need to complicate with local hooks (add-hook 'post-command-hook 'pm-debug-highlight-current-span)) (delete-overlay pm--underline-overlay) (delete-overlay pm--highlight-overlay) (remove-hook 'post-command-hook 'pm-debug-highlight-current-span)))
;;;###autoload(defun pm-debug-minor-mode-on () ;; activating everywhere (in case font-lock infloops in a polymode buffer ) ;; this doesn't activate in fundamental mode (unless (eq major-mode 'minibuffer-inactive-mode) (pm-debug-minor-mode t)))
;;;###autoload(define-globalized-minor-mode pm-debug-mode pm-debug-minor-mode pm-debug-minor-mode-on :group 'polymode)
;;; INFO
(cl-defgeneric pm-debug-info (chunkmode))(cl-defmethod pm-debug-info (chunkmode) (eieio-object-name chunkmode))(cl-defmethod pm-debug-info ((chunkmode pm-inner-chunkmode)) (format "%s head-matcher:\"%s\" tail-matcher:\"%s\"" (cl-call-next-method) (eieio-oref chunkmode 'head-matcher) (eieio-oref chunkmode 'tail-matcher)))(cl-defmethod pm-debug-info ((_chunkmode pm-inner-auto-chunkmode)) (cl-call-next-method))
(defvar syntax-ppss-wide)(defvar syntax-ppss-last)(defun pm--debug-info (&optional span as-list) (let* ((span (or span (and polymode-mode (pm-innermost-span)))) (message-log-max nil) (beg (nth 1 span)) (end (nth 2 span)) (obj (nth 3 span)) (type (and span (or (car span) 'host)))) (let ((out (list (current-buffer) (point-min) (point) (point-max) major-mode type beg end (and obj (pm-debug-info obj)) (format "lppss:%s" (if pm--emacs>26 (car syntax-ppss-wide) syntax-ppss-last))))) (if as-list out (apply #'format "(%s) min:%d pos:%d max:%d || (%s) type:%s span:%s-%s %s %s" out)))))
(defun pm-debug-info-on-current-span (no-cache) "Show info on current span.
With NO-CACHE prefix, don't use cached values of the span."
(interactive "P") (if (not polymode-mode) (message "not in a polymode buffer") (let ((span (pm-innermost-span nil no-cache))) (message (pm--debug-info span)) ;; (move-overlay pm--highlight-overlay (nth 1 span) (nth 2 span) (current-buffer)) (pm-debug-flick-region (nth 1 span) (nth 2 span)))))
(defun pm-debug-report-points (&optional where) (when polymode-mode (let* ((bufs (eieio-oref pm/polymode '-buffers)) (poses (mapcar (lambda (b) (format "%s:%d" b (with-current-buffer b (point)))) bufs))) (message "<%s> cb:%s %s" (or where "") (current-buffer) poses))) nil)
;;; TOGGLING
(defvar pm-debug-display-info-message nil)(defun pm-debug-toogle-info-message () "Toggle permanent info display." (interactive) (setq pm-debug-display-info-message (not pm-debug-display-info-message)))
(defvar poly-lock-allow-fontification)(defun pm-debug-toggle-fontification () "Enable or disable fontification in polymode buffers." (interactive) (if poly-lock-allow-fontification (progn (message "fontificaiton disabled") (dolist (b (buffer-list)) (with-current-buffer b (when polymode-mode (setq poly-lock-allow-fontification nil font-lock-mode nil fontification-functions nil))))) (message "fontificaiton enabled") (dolist (b (buffer-list)) (with-current-buffer b (when polymode-mode (setq poly-lock-allow-fontification t font-lock-mode t fontification-functions '(poly-lock-function)))))))
(defun pm-debug-toggle-after-change () "Allow or disallow polymode actions in `after-change-functions'." (interactive) (if pm-allow-after-change-hook (progn (message "after-change disabled") (setq pm-allow-after-change-hook nil)) (message "after-change enabled") (setq pm-allow-after-change-hook t)))
(defun pm-debug-toggle-post-command () "Allow or disallow polymode actions in `post-command-hook'." (interactive) (if pm-allow-post-command-hook (progn (message "post-command disabled") (setq pm-allow-post-command-hook nil)) (message "post-command enabled") (setq pm-allow-post-command-hook t)))
(defun pm-debug-toggle-all () "Toggle all polymode guards back and forth." (interactive) (if poly-lock-allow-fontification (progn (message "fontificaiton, after-chnage and command-hook disabled") (setq poly-lock-allow-fontification nil pm-allow-after-change-hook nil pm-allow-post-command-hook nil)) (message "fontificaiton, after-change and command-hook enabled") (setq poly-lock-allow-fontification t pm-allow-after-change-hook t pm-allow-post-command-hook t)))
;;; FONT-LOCK
(defun pm-debug-fontify-current-span () "Fontify current span." (interactive) (let ((span (pm-innermost-span)) (poly-lock-allow-fontification t)) (poly-lock-flush (nth 1 span) (nth 2 span)) (poly-lock-fontify-now (nth 1 span) (nth 2 span))))
(defun pm-debug-fontify-current-buffer () "Fontify current buffer." (interactive) (let ((poly-lock-allow-fontification t)) (font-lock-unfontify-buffer) (poly-lock-flush (point-min) (point-max)) (poly-lock-fontify-now (point-min) (point-max))))
;;; TRACING
(defvar pm-traced-functions '( ;; core initialization (0 (pm-initialize pm--common-setup pm--mode-setup)) ;; core hooks (1 (polymode-post-command-select-buffer polymode-after-kill-fixes ;; this one indicates the start of a sequence poly-lock-after-change)) ;; advises (2 (pm-override-output-cons pm-around-advice polymode-with-current-base-buffer)) ;; font-lock (3 (font-lock-default-fontify-region font-lock-fontify-keywords-region font-lock-fontify-region font-lock-fontify-syntactically-region font-lock-unfontify-region jit-lock--run-functions jit-lock-fontify-now poly-lock--after-change-internal poly-lock--extend-region poly-lock--extend-region-span poly-lock-after-change poly-lock-flush poly-lock-fontify-now poly-lock-function)) ;; syntax (4 (syntax-ppss pm--call-syntax-propertize-original polymode-syntax-propertize polymode-restrict-syntax-propertize-extension pm-flush-syntax-ppss-cache pm--reset-ppss-cache)) ;; core functions (5 (pm-select-buffer pm-map-over-spans pm--get-intersected-span pm--cached-span)) ;; (13 . "^syntax-") (14 . "^polymode-") (15 . "^pm-")))
(defvar pm--do-trace nil);;;###autoload(defun pm-toggle-tracing (level) "Toggle polymode tracing.
With numeric prefix toggle tracing for that LEVEL. Currentlyuniversal argument toggles maximum level of tracing (4). Defaultlevel is 3."
(interactive "P") (setq level (prefix-numeric-value (or level 3))) (with-current-buffer (get-buffer-create "*Messages*") (read-only-mode -1)) (setq pm--do-trace (not pm--do-trace)) (if pm--do-trace (progn (dolist (kv pm-traced-functions) (when (<= (car kv) level) (if (stringp (cdr kv)) (pm-trace-functions-by-regexp (cdr kv)) (dolist (fn (cadr kv)) (pm-trace fn))))) (message "Polymode tracing activated")) (untrace-all) (message "Polymode tracing deactivated")))
;;;###autoload(defun pm-trace (fn) "Trace function FN.
Use `untrace-function' to untrace or `untrace-all' to untrace allcurrently traced functions."
(interactive (trace--read-args "Trace: ")) (let ((buff (get-buffer "*Messages*"))) (unless (advice-member-p trace-advice-name fn) (advice-add fn :around (let ((advice (trace-make-advice fn buff 'background #'pm-trace--tracing-context))) (lambda (body &rest args) (when (eq fn 'polymode-flush-syntax-ppss-cache) (with-current-buffer buff (save-excursion (goto-char (point-max)) (insert "\n")))) (if polymode-mode (apply advice body args) (apply body args)))) `((name . ,trace-advice-name) (depth . -100))))))
(defun pm-trace-functions-by-regexp (regexp) "Trace all functions whose name matched REGEXP." (interactive "sRegex: ") (cl-loop for sym being the symbols when (and (fboundp sym) (not (memq sym '(pm-toggle-tracing pm-trace--tracing-context pm-format-span pm-fun-matcher pm--find-tail-from-head))) (not (string-match "^pm-\\(trace\\|debug\\)" (symbol-name sym))) (string-match regexp (symbol-name sym))) do (pm-trace sym)))
(defun pm-trace--tracing-context () (let ((span (or *span* (get-text-property (point) :pm-span)))) (format " [%s pos:%d(%d-%d) %s%s (%f)]" (current-buffer) (point) (point-min) (point-max) (or (when span (when (not (and (= (point-min) (nth 1 span)) (= (point-max) (nth 2 span)))) "UNPR ")) "") (when span (pm-format-span span)) (float-time))))
;; fix object printing(defun pm-trace--fix-1-arg-for-tracing (arg) (cond ((eieio-object-p arg) (eieio-object-name arg)) ((and (listp arg) (eieio-object-p (nth 3 arg))) (list (nth 0 arg) (nth 1 arg) (nth 2 arg) (eieio-object-name (nth 3 arg)))) (arg)))
(defun pm-trace--fix-args-for-tracing (orig-fn fn level args context) (let ((args (or (and (listp args) (listp (cdr args)) (ignore-errors (mapcar #'pm-trace--fix-1-arg-for-tracing args))) args))) (funcall orig-fn fn level args context)))
(advice-add #'trace-entry-message :around #'pm-trace--fix-args-for-tracing)(advice-add #'trace-exit-message :around #'pm-trace--fix-args-for-tracing);; (advice-remove #'trace-entry-message #'pm-trace--fix-args-for-tracing);; (advice-remove #'trace-exit-message #'pm-trace--fix-args-for-tracing)
;;; RELEVANT VARIABLES
(defvar pm-debug-relevant-variables `(:change (before-change-functions after-change-functions) :command (pre-command-hook post-command-hook) :font-lock (fontification-functions font-lock-function font-lock-flush-function font-lock-ensure-function font-lock-fontify-region-function font-lock-fontify-buffer-function font-lock-unfontify-region-function font-lock-unfontify-buffer-function jit-lock-after-change-extend-region-functions jit-lock-functions poly-lock-defer-after-change) ;; If any of these are reset by host mode it can create issues with ;; font-lock and syntax (e.g. scala-mode in #195) :search (parse-sexp-lookup-properties parse-sexp-ignore-comments ;; (syntax-table) ;; font-lock-syntax-table case-fold-search) :indent (indent-line-function indent-region-function pm--indent-line-function-original) :revert (revert-buffer-function before-revert-hook after-revert-hook) :save (after-save-hook before-save-hook write-contents-functions local-write-file-hooks write-file-functions) :syntax (syntax-propertize-function syntax-propertize-extend-region-functions pm--syntax-propertize-function-original)))
;;;###autoload(defun pm-debug-relevant-variables (&optional out-type) "Get the relevant polymode variables.
If OUT-TYPE is 'buffer, print the variables in the dedicatedbuffer, if 'message issue a message, if nil just return a list of values."
(interactive (list 'buffer)) (let* ((cbuff (current-buffer)) (vars (cl-loop for v on pm-debug-relevant-variables by #'cddr collect (cons (car v) (mapcar (lambda (v) (cons v (buffer-local-value v cbuff))) (cadr v)))))) (require 'pp) (cond ((eq out-type 'buffer) (with-current-buffer (get-buffer-create "*polymode-vars*") (erase-buffer) (goto-char (point-max)) (insert (format "\n================== %s ===================\n" cbuff)) (insert (pp-to-string vars)) (toggle-truncate-lines -1) (goto-char (point-max)) (view-mode) (display-buffer (current-buffer)))) ((eq out-type 'message) (message "%s" (pp-to-string vars))) (t vars))))
(defun pm-debug-diff-local-vars (&optional buffer1 buffer2) "Print differences between local variables in BUFFER1 and BUFFER2." (interactive) (let* ((buffer1 (or buffer1 (read-buffer "Buffer1: " (buffer-name (current-buffer))))) (buffer2 (or buffer2 (read-buffer "Buffer2: " (buffer-name (nth 2 (buffer-list)))))) (vars1 (buffer-local-variables (get-buffer buffer1))) (vars2 (buffer-local-variables (get-buffer buffer2))) (all-keys (delete-dups (append (mapcar #'car vars1) (mapcar #'car vars2)))) (out-buf (get-buffer-create "*pm-debug-output"))) (with-current-buffer out-buf (erase-buffer) (pp (delq nil (mapcar (lambda (k) (let ((val1 (cdr (assoc k vars1))) (val2 (cdr (assoc k vars2)))) (unless (equal val1 val2) (list k val1 val2)))) all-keys)) out-buf)) (pop-to-buffer out-buf)))
;;; HIGHLIGHT
(defun pm-debug-highlight-current-span () (when polymode-mode (with-silent-modifications (unless (memq this-command '(pm-debug-info-on-current-span pm-debug-highlight-last-font-lock-error-region)) (delete-overlay pm--highlight-overlay)) (condition-case-unless-debug err (let ((span (pm-innermost-span))) (when pm-debug-display-info-message (message (pm--debug-info span))) (move-overlay pm--underline-overlay (nth 1 span) (nth 2 span) (current-buffer))) (error (message "%s" (error-message-string err)))))))
(defun pm-debug-flick-region (start end &optional delay) (move-overlay pm--highlight-overlay start end (current-buffer)) (run-with-timer (or delay 0.4) nil (lambda () (delete-overlay pm--highlight-overlay))))
(defun pm-debug-map-over-spans-and-highlight () "Map over all spans in the buffer and highlight briefly." (interactive) (pm-map-over-spans (lambda (span) (let ((start (nth 1 span)) (end (nth 2 span))) (pm-debug-flick-region start end) (sit-for 1))) (point-min) (point-max) nil nil t))
(defun pm-debug-map-over-modes-and-highlight (&optional beg end) "Map over all spans between BEG and END and highlight modes." (interactive) (let ((cbuf (current-buffer))) (pm-map-over-modes (lambda (beg end) (goto-char beg) ;; (dbg beg end (pm-format-span)) (with-current-buffer cbuf (recenter-top-bottom) (pm-debug-flick-region (max beg (point-min)) (min end (point-max)))) (sit-for 1)) (or beg (point-min)) (or end (point-max)))))
(defun pm-debug-run-over-check (no-cache) "Map over all spans and report the time taken.
Switch to buffer is performed on every position in the buffer.On prefix NO-CACHE don't use cached spans."
(interactive) (goto-char (point-min)) (let ((start (current-time)) (count 1) (pm-initialization-in-progress no-cache)) (pm-switch-to-buffer) (while (< (point) (point-max)) (setq count (1+ count)) (forward-char) (pm-switch-to-buffer)) (let ((elapsed (float-time (time-subtract (current-time) start)))) (message "Elapsed: %s per-char: %s" elapsed (/ elapsed count)))))
(defun pm-dbg (msg &rest args) (let ((cbuf (current-buffer)) (cpos (point))) (with-current-buffer (get-buffer-create "*pm-dbg*") (save-excursion (goto-char (point-max)) (insert "\n") (insert (apply 'format (concat "%f [%s at %d]: " msg) (float-time) cbuf cpos args))))))
(provide 'polymode-debug);;; polymode-debug.el ends here
|