branch: elpa/logview
commit d25c82ea3a7803a2da5cf63889438f359ea55869
Author: Paul Pogonyshev <pogonys...@gmail.com>
Commit: Paul Pogonyshev <pogonys...@gmail.com>

    Add commands to replace timestamps with their differences compared to 
certain entry.
---
 README.md  |  11 ++++
 TODO.md    |   7 ---
 logview.el | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 178 insertions(+), 33 deletions(-)

diff --git a/README.md b/README.md
index 7a49eddfff..35ff22a631 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,17 @@ See [more detailed description below](#views-explained).
 In Transient Mark mode `h` and `s` operate on region when mark is
 active.
 
+#### Entry timestamp commands
+
+* Replace timestamps with their difference to that of the current
+  entry: `z a`.
+* Same as above, but only within the same thread: `z t`
+* Go to the entry difference to which timestamp is shown: `z z`
+* Don’t show timestamp differences: `z A`
+* Don’t show timestamp differences for this thread: `z T`
+
+Timestamp differences are displayed in seconds.
+
 #### Explicitly hide or show details of individual entries
 
 The mode terms all message lines after the first ‘details’.
diff --git a/TODO.md b/TODO.md
index ebc133d26c..8260c361fe 100644
--- a/TODO.md
+++ b/TODO.md
@@ -20,13 +20,6 @@
   sections can be made to span single request to your server
   (optionally bind to threads too).
 
-* Replace timestamps with difference (likely to section start, as
-  defined above) on demand.  E.g. something like this:
-
-      18:11:03.038 [org.me.MyServer] processing request to 'Foo'
-            +0.003 [org.me.SpecificServlet] initializing
-            +0.004 [org.me.DatabaseUtils] querying the database: '...'
-
 * 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 68a4f6d219..6290e388de 100644
--- a/logview.el
+++ b/logview.el
@@ -7,7 +7,7 @@
 ;; Version:    0.11.2
 ;; Keywords:   files, tools
 ;; Homepage:   https://github.com/doublep/logview
-;; Package-Requires: ((emacs "24.4") (datetime "0.3") (extmap "1.0"))
+;; Package-Requires: ((emacs "24.4") (datetime "0.6.1") (extmap "1.0"))
 
 ;; This program is free software; you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
@@ -545,11 +545,15 @@ this face is used."
                                          eow))
 (defconst logview--timestamp-entry-part-regexp (rx bow "TIMESTAMP" eow))
 
-(defvar logview--datetime-options '(:second-fractional-extension t
-                                    :only-4-digit-years t
-                                    :accept-leading-space t
-                                    :require-leading-zeros t
-                                    :forbid-unnecessary-zeros t))
+(defvar logview--datetime-matching-options '(:second-fractional-extension t
+                                             :only-4-digit-years t
+                                             :accept-leading-space t
+                                             :require-leading-zeros t
+                                             :forbid-unnecessary-zeros t))
+
+(defvar logview--datetime-parsing-options  '(:second-fractional-extension t
+                                             :case-insensitive t
+                                             :lax-whitespace t))
 
 (defvar logview--all-timestamp-formats-cache nil)
 
@@ -569,9 +573,17 @@ this face is used."
 (defvar-local logview--submode-level-faces nil
   "A vector of (ENTRY-FACE . STRING-FACE).")
 
+(defvar-local logview--submode-timestamp-parser           nil)
+(defvar-local logview--timestamp-difference-format-string nil)
+
 (defvar-local logview--as-important-level nil)
 (defvar-local logview--hide-all-details   nil)
 
+(defvar-local logview--timestamp-difference-base nil
+  "Either nil or (POSITION . TIMESTAMP-AS-FLOAT).")
+(defvar-local logview--timestamp-difference-per-thread-bases nil
+  "Either nil or a hash-table of strings to cons cells.")
+
 (defvar-local logview--current-filter-text "")
 (defvar-local logview--current-filter      nil)
 
@@ -687,6 +699,12 @@ this face is used."
      (logview-toggle-details-globally                                        
"Toggle details in the whole buffer")
      (logview-reset-manual-details-hiding                                    
"Show all hidden entry details in the buffer")
      "Here ‘details’ are the message lines after the first.")
+    ("Display timestamp differences"
+     (logview-difference-to-current-entry                                    
"Show difference to the current entry for all other entries")
+     (logview-thread-difference-to-current-entry                             
"Show difference only for the entries of the same thread")
+     (logview-go-to-difference-base-entry                                    
"Go to the entry difference to which timestamp is shown")
+     (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"
      (auto-revert-mode                                                       
"Toggle Auto-Revert mode")
      (auto-revert-tail-mode                                                  
"Toggle Auto-Revert Tail mode")
@@ -904,6 +922,12 @@ that the line is not the first in the buffer."
                        ("D"   logview-toggle-region-entry-details)
                        ("e"   logview-toggle-details-globally)
                        ("r d" logview-reset-manual-details-hiding)
+                       ;; Entry timestamp commands.
+                       ("z a" logview-difference-to-current-entry)
+                       ("z t" logview-thread-difference-to-current-entry)
+                       ("z z" logview-go-to-difference-base-entry)
+                       ("z A" logview-forget-difference-base-entries)
+                       ("z T" logview-forget-thread-difference-base-entry)
                        ;; Option changing commands.
                        ("o r" auto-revert-mode)
                        ("o t" auto-revert-tail-mode)
@@ -964,7 +988,7 @@ successfully.")
 (defun logview--exiting-mode ()
   (logview--std-temporarily-widening
     (logview--std-altering
-      (remove-list-of-text-properties (point-min) (point-max) '(invisible 
logview-entry)))))
+      (remove-list-of-text-properties (point-min) (point-max) '(invisible 
display logview-entry)))))
 
 (defun logview-initialized-p ()
   (not (null logview--entry-regexp)))
@@ -978,14 +1002,21 @@ successfully.")
 
 With prefix argument, additionally put mark at the end of the
 message, which is especially useful for multiline messages.  In
-Transient Mark mode also activate the region."
+Transient Mark mode also activate the region.
+
+If current entry's timestamp is replaced with its difference to
+some base (see e.g.  `logview-difference-to-current-entry'), this
+command additionally shows the real timestamp in the echo area."
   (interactive "P")
   (logview--assert)
   (logview--std-temporarily-widening
     (logview--locate-current-entry entry start
       (goto-char (logview--entry-message-start entry start))
       (when select-message
-        (push-mark (logview--character-back (logview--entry-end entry start)) 
t t)))
+        (push-mark (logview--character-back (logview--entry-end entry start)) 
t t))
+      (when (and (memq 'timestamp logview--submode-features)
+                 (get-text-property (logview--entry-group-start entry start 
logview--timestamp-group) 'display))
+        (message "Timestamp here: %s" (logview--entry-group entry start 
logview--timestamp-group))))
     (unless (and select-message transient-mark-mode)
       (logview--maybe-pulse-current-entry 'message-beginning))))
 
@@ -1746,6 +1777,82 @@ which haven't been manually hidden are visible."
   (logview--update-invisibility-spec))
 
 
+
+
+(defun logview-difference-to-current-entry ()
+  "Display difference to current entry's timestamp.
+Difference is shown for all other entries.  Any thread-specific
+difference bases (appointed with 
`\\<logview-mode-map>\\[logview-thread-difference-to-current-entry]') are 
removed."
+  (interactive)
+  (logview--assert 'timestamp)
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry start
+      (unless (and (equal (car logview--timestamp-difference-base) start) 
(null logview--timestamp-difference-per-thread-bases))
+        (setq logview--timestamp-difference-base             
(logview--difference-base entry start)
+              logview--timestamp-difference-per-thread-bases nil)
+        (logview--refontify-buffer)))))
+
+(defun logview-thread-difference-to-current-entry ()
+  "Display difference to current entry's timestamp in its thread.
+In case there is a global difference base (appointed with 
`\\<logview-mode-map>\\[logview-difference-to-current-entry]'),
+it stays in effect for other threads."
+  (interactive)
+  (logview--assert 'timestamp 'thread)
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry start
+      (let ((thread (logview--entry-group entry start logview--thread-group)))
+        (unless (and logview--timestamp-difference-per-thread-bases (equal 
(car (gethash thread logview--timestamp-difference-per-thread-bases)) start))
+          (unless logview--timestamp-difference-per-thread-bases
+            (setq logview--timestamp-difference-per-thread-bases 
(make-hash-table :test #'equal)))
+          (puthash thread (logview--difference-base entry start) 
logview--timestamp-difference-per-thread-bases)
+          (logview--refontify-buffer))))))
+
+(defun logview-go-to-difference-base-entry ()
+  (interactive)
+  (logview--assert 'timestamp)
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry start
+      (let* ((thread          (logview--entry-group entry start 
logview--thread-group))
+             (difference-base (car (or (when 
logview--timestamp-difference-per-thread-bases
+                                         (gethash thread 
logview--timestamp-difference-per-thread-bases))
+                                       logview--timestamp-difference-base))))
+        (unless difference-base
+          (user-error "There is no timestamp difference base for the current 
entry"))
+        (when (invisible-p difference-base)
+          (user-error "Timestamp difference base for the current entry is 
currently hidden"))
+        (let* ((entry+start (logview--do-locate-current-entry difference-base))
+               (entry       (car entry+start))
+               (start       (cdr entry+start)))
+          (unless (and (< start (logview--point-max)) (> (logview--entry-end 
entry start) (logview--point-min)))
+            (user-error "Difference base entry is outside the narrowing 
region"))
+          (goto-char (logview--entry-message-start entry start))
+          (logview--maybe-pulse-current-entry 'movement))))))
+
+(defun logview-forget-difference-base-entries ()
+  (interactive)
+  (logview--assert 'timestamp)
+  (unless (and (null logview--timestamp-difference-base) (null 
logview--timestamp-difference-per-thread-bases))
+    (setq logview--timestamp-difference-base             nil
+          logview--timestamp-difference-per-thread-bases nil)
+    (logview--refontify-buffer)))
+
+(defun logview-forget-thread-difference-base-entry ()
+  (interactive)
+  (logview--assert 'timestamp 'thread)
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry start
+      (let* ((thread          (logview--entry-group entry start 
logview--thread-group))
+             (difference-base (when 
logview--timestamp-difference-per-thread-bases
+                                (gethash thread 
logview--timestamp-difference-per-thread-bases))))
+        (unless difference-base
+          (user-error "There is no thread-specific timestamp difference base 
for thread `%s'" thread))
+        (remhash thread logview--timestamp-difference-per-thread-bases)
+        (logview--refontify-buffer)))))
+
+(defun logview--difference-base (entry start)
+  (cons start (funcall logview--submode-timestamp-parser (logview--entry-group 
entry start logview--timestamp-group))))
+
+
 
 ;;; Option changing commands.
 
@@ -2130,11 +2237,12 @@ returns non-nil."
     (unless cannot-match
       (dolist (timestamp-option (if timestamp-at timestamp-options '(nil)))
         (let* ((timestamp-pattern (assq 'java-pattern timestamp-option))
+               (timestamp-locale  (cdr (assq 'locale timestamp-option)))
                (timestamp-regexp  (if timestamp-pattern
                                       (condition-case error
                                           (apply #'datetime-matching-regexp 
'java (cdr timestamp-pattern)
-                                                 :locale (cdr (assq 'locale 
timestamp-option))
-                                                 (append (cdr (assq 
'datetime-options timestamp-option)) logview--datetime-options))
+                                                 :locale timestamp-locale
+                                                 (append (cdr (assq 
'datetime-options timestamp-option)) logview--datetime-matching-options))
                                         ;; 'datetime' doesn't mention the 
erroneous pattern to keep
                                         ;; the error message concise.  Let's 
do it ourselves.
                                         (error (warn "In Java timestamp 
pattern '%s': %s"
@@ -2163,6 +2271,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))))
                 (read-only-mode 1)
                 (when buffer-file-name
                   (pcase logview-auto-revert-mode
@@ -2210,7 +2325,7 @@ returns non-nil."
                                  (key              (cons pattern (when 
locale-dependent locale))))
                             (when (or locale-dependent (null (gethash key 
patterns)))
                               (puthash key
-                                       (apply #'datetime-matching-regexp 
'parsed parts :locale locale logview--datetime-options)
+                                       (apply #'datetime-matching-regexp 
'parsed parts :locale locale logview--datetime-matching-options)
                                        patterns)))))))))))
           (maphash (lambda (key regexp)
                      (let ((existing (gethash regexp uniques)))
@@ -2235,9 +2350,10 @@ returns non-nil."
     (user-error "Couldn't determine log format; press C-c C-s to customize 
relevant options"))
   (dolist (assertion assertions)
     (unless (or (eq assertion 'message) (memq assertion 
logview--submode-features))
-      (user-error (cdr (assq assertion '((level  . "Log lacks entry levels")
-                                         (name   . "Log lacks logger names")
-                                         (thread . "Log doesn't include thread 
names"))))))))
+      (user-error (cdr (assq assertion '((level     . "Log lacks entry levels")
+                                         (name      . "Log lacks logger names")
+                                         (thread    . "Log doesn't include 
thread names")
+                                         (timestamp . "Log entries lack 
timestamps"))))))))
 
 
 (defun logview--forward-entry (&optional n validator)
@@ -2783,14 +2899,18 @@ This list is preserved across Emacs session in
           (setq region-start first-entry-start)
           (logview--std-altering
             (save-match-data
-              (let ((have-timestamp   (memq 'timestamp 
logview--submode-features))
-                    (have-level       (memq 'level     
logview--submode-features))
-                    (have-name        (memq 'name      
logview--submode-features))
-                    (have-thread      (memq 'thread    
logview--submode-features))
-                    (validator        (cdr logview--current-filter))
-                    (highlighter      (cdr logview--highlighted-filter))
-                    (highlighted-part logview-highlighted-entry-part)
-                    found-anything-visible)
+              (let* ((have-timestamp              (memq 'timestamp 
logview--submode-features))
+                     (have-level                  (memq 'level     
logview--submode-features))
+                     (have-name                   (memq 'name      
logview--submode-features))
+                     (have-thread                 (memq 'thread    
logview--submode-features))
+                     (validator                   (cdr 
logview--current-filter))
+                     (difference-base             
logview--timestamp-difference-base)
+                     (difference-bases-per-thread 
logview--timestamp-difference-per-thread-bases)
+                     (displaying-differences      (or difference-base 
difference-bases-per-thread))
+                     (difference-format-string    
logview--timestamp-difference-format-string)
+                     (highlighter                 (cdr 
logview--highlighted-filter))
+                     (highlighted-part            
logview-highlighted-entry-part)
+                     found-anything-visible)
                 (logview--iterate-entries-forward
                  region-start
                  (lambda (entry start)
@@ -2805,9 +2925,30 @@ This list is preserved across Emacs session in
                                                        
(logview--entry-group-end   entry start logview--level-group)
                                                        (cdr entry-faces))))
                            (when have-timestamp
-                             (add-face-text-property 
(logview--entry-group-start entry start logview--timestamp-group)
-                                                     (logview--entry-group-end 
  entry start logview--timestamp-group)
-                                                     'logview-timestamp))
+                             (let ((from (logview--entry-group-start entry 
start logview--timestamp-group))
+                                   (to   (logview--entry-group-end   entry 
start logview--timestamp-group))
+                                   timestamp-replaced)
+                               (add-face-text-property from to 
'logview-timestamp)
+                               (when displaying-differences
+                                 (let ((difference-base (or (when 
difference-bases-per-thread
+                                                              (gethash 
(logview--entry-group entry start logview--thread-group) 
difference-bases-per-thread))
+                                                            difference-base)))
+                                   (when (and difference-base (not (= (car 
difference-base) start)))
+                                     ;; FIXME: It is possible that fractionals 
are not the last
+                                     ;;        thing in the timestamp, in 
which case it would be
+                                     ;;        nicer to add some spaces on the 
right. However,
+                                     ;;        it's not easy to do and is also 
quite unlikely,
+                                     ;;        so ignoring that for now.
+                                     (let* ((difference        (- (funcall 
logview--submode-timestamp-parser (buffer-substring-no-properties from to))
+                                                                  (cdr 
difference-base)))
+                                            (difference-string (format 
difference-format-string difference))
+                                            (length-delta      (- to from 
(length difference-string))))
+                                       (when (> length-delta 0)
+                                         (setq difference-string (concat 
(make-string length-delta ? ) difference-string)))
+                                       (put-text-property from to 'display 
difference-string)
+                                       (setq timestamp-replaced t)))))
+                               (unless timestamp-replaced
+                                 (remove-list-of-text-properties from to 
'(display)))))
                            (when have-name
                              (add-face-text-property 
(logview--entry-group-start entry start logview--name-group)
                                                      (logview--entry-group-end 
  entry start logview--name-group)

Reply via email to