branch: elpa/logview commit 0bd305779c09e52fd136edf6d0acb85501251cdb Author: Paul Pogonyshev <pogonys...@gmail.com> Commit: Paul Pogonyshev <pogonys...@gmail.com>
Add commands to jump to next/previous large gap in entry timestamps (requested as issue #5). --- README.md | 5 ++ TODO.md | 4 -- logview.el | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 154 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 35ff22a631..43698a7afd 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,10 @@ active again. * Move to next / previous entry: `n` / `p` * Move to next / previous ‘as important’ [*] entry: `N` / `P` * Move to next / previous entry in the navigation view: `M-n` / `M-p` +* Move to the next / previous entry with large timestamp gap after the + previous: `z n` / `z p` +* Same as above, but only considering entries in the same thread: `z + N` / `z P` * Move to first / last entry: `<` / `>` [*] ‘As important’ means entries with the same or higher level. For @@ -210,6 +214,7 @@ In Transient Mark mode `d` operates on region when mark is active. These options can be customized globally and additionally temporarily changed in each individual buffer. +* Change gap length for `z n` and similar commands: `o g` or `z g` * Toggle Auto-Revert mode: `o r` * Toggle Auto-Revert Tail mode: `o t` * Toggle ‘copy only visible text’: `o v` diff --git a/TODO.md b/TODO.md index 8260c361fe..5dd4842ffd 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,3 @@ to the section start, narrow to section etc. The idea is that sections can be made to span single request to your server (optionally bind to threads too). - -* Add a command to find big gaps in timestamps. Alternatively or in - addition to the requested jumping, it could also be used to define - sections. See https://github.com/doublep/logview/issues/5 diff --git a/logview.el b/logview.el index 13c0b53b16..574cff9af8 100644 --- a/logview.el +++ b/logview.el @@ -344,6 +344,14 @@ will refuse to complete operation unless this check succeeds." :type 'integer) +(defcustom logview-target-gap-length 60 + "Default target gap length for `\\<logview-mode-map>\\[logview-next-timestamp-gap]' and similar commands. + +This must be a non-negative number of seconds. Can be changed +temporarily for a single buffer with `\\<logview-mode-map>\\[logview-change-target-gap-length]'." + :group 'logview + :type 'number) + (defcustom logview-copy-visible-text-only t "Whether to copy, kill, etc. only visible selected text. Standard Emacs behavior is to copy even invisible text, but that @@ -379,13 +387,14 @@ To temporarily change this on per-buffer basis type `\\<logview-mode-map>\\[logv :set 'logview--set-highlight-affecting-variable) -(defcustom logview-pulse-entries '(navigation-view) +(defcustom logview-pulse-entries '(navigation-view timestamp-gap) "When to briefly highlight the current entry. You can also pulse the current entry unconditionally with `\\<logview-mode-map>\\[logview-pulse-current-entry]' command." :group 'logview :type '(set :inline t (const :tag "After navigating a view with `\\<logview-mode-map>\\[logview-next-navigation-view-entry]' or `\\<logview-mode-map>\\[logview-previous-navigation-view-entry]'" navigation-view) (const :tag "After navigating within the current entry with `\\<logview-mode-map>\\[logview-go-to-message-beginning]'" message-beginning) + (const :tag "After finding large gaps in entry timestamps (`\\<logview-mode-map>\\[logview-next-timestamp-gap]' and similar)" timestamp-gap) (const :tag "After other entry movement commands" movement))) (defcustom logview-views-file (locate-user-emacs-file "logview.views") @@ -575,6 +584,7 @@ this face is used." (defvar-local logview--submode-timestamp-parser nil) (defvar-local logview--timestamp-difference-format-string nil) +(defvar-local logview--timestamp-gap-format-string nil) (defvar-local logview--as-important-level nil) (defvar-local logview--hide-all-details nil) @@ -584,6 +594,9 @@ this face is used." (defvar-local logview--timestamp-difference-per-thread-bases nil "Either nil or a hash-table of strings to cons cells.") +(defvar-local logview--buffer-target-gap-length nil) +(defvar-local logview--last-found-large-gap nil) + (defvar-local logview--current-filter-text "") (defvar-local logview--current-filter nil) @@ -646,6 +659,8 @@ this face is used." (logview-next-entry logview-previous-entry "Next / previous entry") (logview-next-as-important-entry logview-previous-as-important-entry "Next / previous ‘as important’ entry") (logview-next-navigation-view-entry logview-previous-navigation-view-entry "Next / previous entry in the navigation view (see below)") + (logview-next-timestamp-gap logview-previous-timestamp-gap "Next / previous large gap in entry timestamps") + (logview-next-timestamp-gap-in-this-thread logview-next-timestamp-gap-in-this-thread "Same, but only within this thread") (logview-first-entry logview-last-entry "First / last entry") "‘As important’ means entries with the same or higher level.") ("Narrowing and widening" @@ -706,6 +721,7 @@ this face is used." (logview-forget-difference-base-entries "Don’t show timestamp differences") (logview-forget-thread-difference-base-entry "Don’t show timestamp differences for this thread")) ("Change options for current buffer" + (logview-change-target-gap-length "Set gap length for ‘\\[logview-next-timestamp-gap]’ and similar commands") (auto-revert-mode "Toggle Auto-Revert mode") (auto-revert-tail-mode "Toggle Auto-Revert Tail mode") (logview-toggle-copy-visible-text-only "Toggle ‘copy only visible text’") @@ -934,12 +950,18 @@ that the line is not the first in the buffer." ("z z" logview-go-to-difference-base-entry) ("z A" logview-forget-difference-base-entries) ("z T" logview-forget-thread-difference-base-entry) + ("z n" logview-next-timestamp-gap) + ("z p" logview-previous-timestamp-gap) + ("z N" logview-next-timestamp-gap-in-this-thread) + ("z P" logview-previous-timestamp-gap-in-this-thread) + ("z g" logview-change-target-gap-length) ;; Option changing commands. ("o r" auto-revert-mode) ("o t" auto-revert-tail-mode) ("o v" logview-toggle-copy-visible-text-only) ("o m" logview-toggle-search-only-in-messages) ("o e" logview-toggle-show-ellipses) + ("o g" logview-change-target-gap-length) ("o s" logview-choose-submode) ("o S" logview-customize-submode-options) ;; For compatibility with the inactive keymap. @@ -1855,6 +1877,112 @@ it stays in effect for other threads." (logview--refontify-buffer))))) + + +(defun logview-next-timestamp-gap (&optional n) + "Move to the next large gap in entry timestamps. +If N is specified, use that as a gap count. Filtered out entries +are ignored. + +Point is positioned at the beginning of the message of the first +entry after the gap. If there is a large enough gap just after +the current entry and N is one, this command just moves one entry +down." + (interactive "p") + (logview--do-next-timestamp-gap n nil)) + +(defun logview-previous-timestamp-gap (&optional n) + "Move to the previous large gap in entry timestamps. +If N is specified, use that as a gap count. Filtered out entries +are ignored. + +Point is positioned at the beginning of the message of the first +entry after the gap." + (interactive "p") + (logview--do-next-timestamp-gap (if n (- n) -1) nil)) + +(defun logview-next-timestamp-gap-in-this-thread (&optional n) + "Move to the next large gap in thread's entry timestamps. +If N is specified, use that as a gap count. Only consider +entries within this thread that are not filtered out. + +Point is positioned at the beginning of the message of the first +entry after the gap." + (interactive "p") + (logview--do-next-timestamp-gap n t)) + +(defun logview-previous-timestamp-gap-in-this-thread (&optional n) + "Move to the previous large gap in thread's entry timestamps. +If N is specified, use that as a gap count. Only consider +entries within this thread that are not filtered out. + +Point is positioned at the beginning of the message of the first +entry after the gap." + (interactive "p") + (logview--do-next-timestamp-gap (if n (- n) -1) t)) + +(defun logview--do-next-timestamp-gap (n same-thread-only) + (logview--assert 'timestamp) + (when same-thread-only + (logview--assert 'thread)) + (unless n + (setq n 1)) + (logview--locate-current-entry started-at-entry started-at + (let ((thread (when same-thread-only + (logview--entry-group started-at-entry started-at logview--thread-group)))) + (when (< n 0) + (logview--forward-entry -1 (when thread + (lambda (entry start) + (string-equal (logview--entry-group entry start logview--thread-group) thread))))) + (logview--std-temporarily-widening + (logview--locate-current-entry entry start + (let ((remaining (logview--forward-entry n (logview--entry-timestamp-gap-validator entry start thread))) + (success (when logview--last-found-large-gap + (format "Gap to the previous entry%s is %s s" + (if same-thread-only " of this thread" "") + (format logview--timestamp-gap-format-string logview--last-found-large-gap)))) + (failure (format "No more large enough (%s s or more) gaps in timestamps%s" + (logview--target-gap-length) (if same-thread-only " in this thread" "")))) + (when (< n 0) + (if (= remaining n) + (goto-char (logview--entry-message-start started-at-entry started-at)) + (logview--forward-entry 1))) + (logview--maybe-pulse-current-entry 'timestamp-gap) + (cond ((= n 0)) + ((= remaining n) + (user-error "%s" failure)) + ((= remaining 0) + (message "%s" success)) + (t + (user-error "%s; %s" success (downcase failure)))))))))) + +(defun logview-change-target-gap-length (length) + (interactive (list (if current-prefix-arg + (prefix-numeric-value current-prefix-arg) + (string-to-number (read-from-minibuffer (format "Search for gap this long (currently %s s): " (logview--target-gap-length))))))) + (if (and (numberp length) (>= length 0)) + (setq logview--buffer-target-gap-length (when (/= length 0) length)) + (user-error "Expected a non-negative positive number"))) + +(defun logview--entry-timestamp-gap-validator (entry start thread) + (let ((timestamp (logview--entry-timestamp entry start)) + (target-gap (logview--target-gap-length))) + (lambda (entry start) + (when (or (null thread) (string-equal (logview--entry-group entry start logview--thread-group) thread)) + (let* ((new-timestamp (logview--entry-timestamp entry start)) + (gap (abs (- new-timestamp timestamp)))) + (setq timestamp new-timestamp) + (when (>= gap target-gap) + (setq logview--last-found-large-gap gap))))))) + +(defun logview--target-gap-length () + (let ((target-gap (or logview--buffer-target-gap-length logview-target-gap-length))) + (if (and (numberp target-gap) (> target-gap 0)) + target-gap + ;; Might want to issue a user error instead. + 60))) + + ;;; Option changing commands. @@ -1978,23 +2106,25 @@ These are: (dolist (entry (cdr section)) (if (listp entry) (insert " " (logview--help-format-keys entry "[1-5]" keys-width) - " " (car (last entry)) "\n") - (insert "\n " (replace-regexp-in-string (rx "\\[" - (group (1+ (any alnum ?-))) - (? " <" (group (1+ (not (any ?>)))) ">") - "]") - (lambda (text) - (save-match-data - (logview--help-format-keys (list (intern (match-string 1 text))) (match-string 2 text)))) - entry t t) - "\n"))))))) + " " (logview--help-substitute-keys (car (last entry))) "\n") + (insert "\n " (logview--help-substitute-keys entry) "\n"))))))) (goto-char 1) (help-mode) (let ((map (make-sparse-keymap))) (set-keymap-parent map help-mode-map) (substitute-key-definition 'revert-buffer 'undefined map help-mode-map) (use-local-map map))) - (pop-to-buffer "*Logview cheat sheet*")) + (pop-to-buffer "*Logview cheat sheet*")) + +(defun logview--help-substitute-keys (text) + (replace-regexp-in-string (rx "\\[" + (group (1+ (any alnum ?-))) + (? " <" (group (1+ (not (any ?>)))) ">") + "]") + (lambda (text) + (save-match-data + (logview--help-format-keys (list (intern (match-string 1 text))) (match-string 2 text)))) + text t t)) (defun logview--help-format-keys (entry &optional preferred-keys width) (if (listp entry) @@ -2273,13 +2403,13 @@ returns non-nil." (setq logview--submode-level-faces (make-vector level-index nil)) (dolist (level-data logview--submode-level-data) (aset logview--submode-level-faces (cadr level-data) (cddr level-data))) - (setq logview--submode-timestamp-parser (when (memq 'timestamp features) - (apply #'datetime-parser-to-float 'java (cdr timestamp-pattern) - :locale timestamp-locale :timezone 'system - logview--datetime-parsing-options)) - logview--timestamp-difference-format-string (when (memq 'timestamp features) - (format "%%+.%df" (apply #'datetime-pattern-num-second-fractionals 'java (cdr timestamp-pattern) - logview--datetime-parsing-options)))) + (when (memq 'timestamp features) + (let ((num-fractionals (apply #'datetime-pattern-num-second-fractionals 'java (cdr timestamp-pattern) logview--datetime-parsing-options))) + (setq logview--submode-timestamp-parser (apply #'datetime-parser-to-float 'java (cdr timestamp-pattern) + :locale timestamp-locale :timezone 'system + logview--datetime-parsing-options) + logview--timestamp-difference-format-string (format "%%+.%df" num-fractionals) + logview--timestamp-gap-format-string (format "%%.%df" num-fractionals)))) (read-only-mode 1) (when buffer-file-name (pcase logview-auto-revert-mode