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

    Merge filtering and entry parsing.
    
    This makes initial buffer parsing somewhat slower, but automatically
    applies current filters to the newly inserted text, which is
    especially important in (tail) auto-revert mode.
---
 logview.el | 344 +++++++++++++++++++++++++++++--------------------------------
 1 file changed, 164 insertions(+), 180 deletions(-)

diff --git a/logview.el b/logview.el
index 786db472b9..25ca304b86 100644
--- a/logview.el
+++ b/logview.el
@@ -430,14 +430,16 @@ Levels are ordered least to most important.")
 (defvar-local logview--min-shown-level nil)
 (defvar-local logview--as-important-levels nil)
 
-(defvar-local logview--applied-filters '(nil nil nil nil nil nil))
-(defvar-local logview--all-current-filters    nil)
-(defvar-local logview--include-name-regexp    nil)
-(defvar-local logview--exclude-name-regexp    nil)
-(defvar-local logview--include-thread-regexp  nil)
-(defvar-local logview--exclude-thread-regexp  nil)
-(defvar-local logview--include-message-regexp nil)
-(defvar-local logview--exclude-message-regexp nil)
+(defvar-local logview--current-filter-text nil)
+
+;; Currently a list of three items: matching function, inclusion and
+;; exclusion regexps.
+(defvar-local logview--name-filter    nil)
+(defvar-local logview--thread-filter  nil)
+(defvar-local logview--message-filter nil)
+
+(defvar logview--empty-filter-id '((nil nil) (nil nil) (nil nil)))
+(defvar-local logview--applied-filter-id logview--empty-filter-id)
 
 (defvar logview--name-regexp-history)
 (defvar logview--thread-regexp-history)
@@ -850,7 +852,7 @@ hidden."
   (interactive)
   (let ((self    (current-buffer))
         (windows (current-window-configuration))
-        (filters logview--all-current-filters))
+        (filters logview--current-filter-text))
     (unless (buffer-live-p logview--filter-editing-buffer)
       (setq logview--filter-editing-buffer (generate-new-buffer (format "%s: 
Filters" (buffer-name)))))
     (split-window-vertically)
@@ -923,9 +925,9 @@ that doesn't match any of entered expression."
       (error "Invalid regular expression"))
     (when (and (memq type '(name thread)) (string-match "\n" regexp))
       (error "Regular expression must not span several lines"))
-    (setq logview--all-current-filters (concat logview--all-current-filters
-                                               (when (and 
logview--all-current-filters
-                                                          (not 
(string-suffix-p "\n" logview--all-current-filters)))
+    (setq logview--current-filter-text (concat logview--current-filter-text
+                                               (when (and 
logview--current-filter-text
+                                                          (not 
(string-suffix-p "\n" logview--current-filter-text)))
                                                  "\n")
                                                filter-line-prefix " " 
(replace-regexp-in-string "\n" "\n.. " regexp) "\n"))
     (logview--parse-filters)
@@ -1026,7 +1028,7 @@ hiding."
   (interactive "r")
   (logview--assert)
   (logview--std-matching-and-altering
-    (logview--iterate-entries-in-region begin end 
(logview--hide-entry-callback 'logview-hidden-entry))) )
+    (logview--iterate-entries-in-region begin end 
(logview--hide-entry-callback 'logview-hidden-entry))))
 
 (defun logview-show-entries (&optional n)
   "Show explicitly hidden entries.
@@ -1069,7 +1071,7 @@ entries in the region instead (i.e. work just like
   (interactive "r")
   (logview--assert)
   (logview--std-matching-and-altering
-    (logview--iterate-entries-in-region begin end 
(logview--show-entry-callback 'logview-hidden-entry))) )
+    (logview--iterate-entries-in-region begin end 
(logview--show-entry-callback 'logview-hidden-entry))))
 
 
 
@@ -1316,7 +1318,8 @@ data and must make sure point and match data are 
preserved.
 
 If ONLY-VISIBLE is non-nil, hidden entries are skipped.  If
 VALIDATOR is non-nil, entries for which the function returns nil
-are skipped too."
+are skipped too.  VALIDATOR is always called with match data set
+and point at the beginning of the next line."
   (when (logview--match-current-entry)
     (let ((entry-begin (match-beginning 0))
           (after-first-line)
@@ -1324,9 +1327,9 @@ are skipped too."
           (limit       (point-max))
           (invalid))
       (while (progn
+               (forward-line)
                (setq invalid (or (and only-visible (invisible-p entry-begin))
                                  (and validator    (not (funcall validator)))))
-               (forward-line)
                (setq after-first-line (point)
                      entry-end        (if (re-search-forward 
logview--entry-regexp nil t)
                                           (match-beginning 0)
@@ -1350,9 +1353,10 @@ See `logview--iterate-entries-forward' for details."
                   (progn
                     (setq entry-end   entry-begin
                           entry-begin (match-beginning 0))
+                    (forward-line)
                     (or (and only-visible (invisible-p entry-begin))
                         (and validator    (not (funcall validator)))
-                        (when (funcall callback entry-begin (progn 
(forward-line) (point)) entry-end)
+                        (when (funcall callback entry-begin (point) entry-end)
                           (goto-char entry-begin)))))))))
 
 (defun logview--iterate-successive-entries (n callback &optional only-visible 
validator)
@@ -1398,7 +1402,7 @@ See `logview--iterate-entries-forward' for details."
 (defun logview--parse-filters (&optional to-reset)
   ;; As we "leave" current buffer, we need to rebind variables
   ;; locally, so their values are properly transferred.
-  (let ((filters logview--all-current-filters)
+  (let ((filters logview--current-filter-text)
         non-discarded-lines
         include-name-regexps
         exclude-name-regexps
@@ -1429,13 +1433,10 @@ See `logview--iterate-entries-forward' for details."
                      ("m+" (push regexp include-message-regexps))
                      ("m-" (push regexp exclude-message-regexps))))))
              t))))
-      (setq logview--all-current-filters    (apply 'concat (nreverse 
non-discarded-lines))
-            logview--include-name-regexp    (logview--build-filter-regexp 
include-name-regexps)
-            logview--exclude-name-regexp    (logview--build-filter-regexp 
exclude-name-regexps)
-            logview--include-thread-regexp  (logview--build-filter-regexp 
include-thread-regexps)
-            logview--exclude-thread-regexp  (logview--build-filter-regexp 
exclude-thread-regexps)
-            logview--include-message-regexp (logview--build-filter-regexp 
include-message-regexps)
-            logview--exclude-message-regexp (logview--build-filter-regexp 
exclude-message-regexps)))))
+      (setq logview--current-filter-text (apply 'concat (nreverse 
non-discarded-lines))
+            logview--name-filter         (logview--build-filter 
include-name-regexps    exclude-name-regexps)
+            logview--thread-filter       (logview--build-filter 
include-thread-regexps  exclude-thread-regexps)
+            logview--message-filter      (logview--build-filter 
include-message-regexps exclude-message-regexps)))))
 
 (defun logview--iterate-filter-lines (callback)
   "Find successive filter specification in the current buffer.
@@ -1470,106 +1471,153 @@ which is usually one line beyond END."
                       (forward-line)))
                   (funcall callback type line-begin begin (if (bolp) 
(logview--linefeed-back-checked (point)) (point))))))))
 
+(defun logview--build-filter (include-regexp-list exclude-regexp-list)
+  (let ((include-regexp (logview--build-filter-regexp include-regexp-list))
+        (exclude-regexp (logview--build-filter-regexp exclude-regexp-list)))
+    (list (if include-regexp
+              (if exclude-regexp
+                  (lambda (string) (and (string-match include-regexp string) 
(not (string-match exclude-regexp string))))
+                (lambda (string) (string-match include-regexp string)))
+            (when exclude-regexp
+              (lambda (string) (not (string-match exclude-regexp string)))))
+          include-regexp
+          exclude-regexp)))
+
 (defun logview--filter-regexp (begin end)
   (replace-regexp-in-string "\n\\.\\. " "\n" (buffer-substring-no-properties 
begin end)))
 
-(defun logview--apply-parsed-filters (&optional also-cancel-explicit-hiding)
-  (let* ((include-name-regexp    logview--include-name-regexp)
-         (exclude-name-regexp    logview--exclude-name-regexp)
-         (include-thread-regexp  logview--include-thread-regexp)
-         (exclude-thread-regexp  logview--exclude-thread-regexp)
-         (include-message-regexp logview--include-message-regexp)
-         (exclude-message-regexp logview--exclude-message-regexp)
-         (no-message-filter      (not (or include-message-regexp 
exclude-message-regexp)))
-         (filters                (list include-name-regexp    
exclude-name-regexp
-                                       include-thread-regexp  
exclude-thread-regexp
-                                       include-message-regexp 
exclude-message-regexp))
+(defun logview--apply-parsed-filters (&optional cancel-explicit-hiding)
+  (logview--process-region-entries 1 (1+ (buffer-size)) nil 
cancel-explicit-hiding
+                                   (lambda (begin end) (make-progress-reporter 
"Filtering..." begin end))))
+
+(defun logview--process-region-entries (region-begin region-end set-up-entries 
cancel-explicit-hiding reporter-builder)
+  (let* ((name-filter            (car logview--name-filter))
+         (thread-filter          (car logview--thread-filter))
+         (no-name/thread-filters (and (null name-filter) (null thread-filter)))
+         (message-filter         (car logview--message-filter))
+         (filter-id              (list (cdr logview--name-filter) (cdr 
logview--thread-filter) (cdr logview--message-filter)))
          ;; Need a copy, since entry matching is always case-sensitive
          ;; (see 'logview--std-matching-and-altering').
          (case-insensitive       case-fold-search))
-    (when (or (not (equal logview--applied-filters filters)) 
also-cancel-explicit-hiding)
+    (when (or set-up-entries cancel-explicit-hiding (not (equal 
logview--applied-filter-id filter-id)))
       (logview--std-matching-and-altering
         (save-restriction
           (widen)
-          (goto-char (point-min))
-          (let ((reporter           (make-progress-reporter "Filtering..." 
(point-min) (point-max) (point)))
-                (hider              (logview--hide-entry-callback 
'logview-filtered))
-                (shower             (logview--show-entry-callback 
'logview-filtered))
-                (explicit-shower    (and also-cancel-explicit-hiding 
(logview--show-entry-callback 'logview-hidden-entry)))
-                (num-hidden         0)
-                (num-visible        0)
-                (match-data-storage '(nil))
-                message-begin
-                matches-name/thread)
-            ;; Because 'callback' doesn't get access to match data,
-            ;; while in 'validator' doesn't know all entry limits, we
-            ;; use both and pass 'matches-name/thread' from the
-            ;; validator to the callback.
-            (logview--iterate-entries-forward
-             (lambda (begin after-first-line end)
-               ;; Remember that 'matches-name/thread' is not the final
-               ;; value, we still need to check if entry's message
-               ;; passes filters.
-               (if (and matches-name/thread
-                        (or no-message-filter
-                            ;; Ideally would just match in the buffer
-                            ;; itself, but that's probably unsound due
-                            ;; to anchors.
-                            (let ((message          
(buffer-substring-no-properties message-begin end))
-                                  (case-fold-search case-insensitive))
-                              ;; For speed optimization we don't use
-                              ;; 'save-match-data'.
-                              (match-data t match-data-storage)
-                              (prog1
-                                  (and (or (null include-message-regexp)
-                                           (string-match 
include-message-regexp message))
-                                       (or (null exclude-message-regexp)
-                                           (not (string-match 
exclude-message-regexp message))))
-                                (set-match-data match-data-storage)))))
-                   (progn (funcall shower begin after-first-line end)
-                          (setq num-visible (1+ num-visible)))
-                 (funcall hider begin after-first-line end)
-                 (setq num-hidden (1+ num-hidden)))
-               ;; Yeah, it's two modification of properties on the
-               ;; same text chunk, but that's rarely used and so
-               ;; hardly important.
-               (when explicit-shower
-                 (funcall explicit-shower begin after-first-line end))
-               (progress-reporter-update reporter end)
-               ;; Always continue.
-               t)
-             nil
-             (lambda ()
-               (let ((name             (match-string logview--name-group))
-                     (thread           (match-string logview--thread-group))
-                     (case-fold-search case-insensitive))
-                 (setq message-begin       (match-end 0)
-                       matches-name/thread (and (or (null include-name-regexp)
-                                                    (string-match 
include-name-regexp name))
-                                                (or (null exclude-name-regexp)
-                                                    (not (string-match 
exclude-name-regexp name)))
-                                                (or (null 
include-thread-regexp)
-                                                    (string-match 
include-thread-regexp thread))
-                                                (or (null 
exclude-thread-regexp)
-                                                    (not (string-match 
exclude-thread-regexp thread))))))
-               ;; Operate on all entries.
-               t))
-            (cond ((= num-hidden 0)
-                   (message (if (cl-every 'null filters) "Filters are reset" 
"Filtering complete, nothing was hidden")))
-                  ((= num-visible 0)
-                   (message "Filtering complete, all entries were hidden"))
-                  (t
-                   (message "Filtering complete, %d %s out of %d (%.1f%%) %s 
hidden"
-                            num-hidden (if (= num-hidden 1) "entry" "entries") 
(+ num-hidden num-visible)
-                            (/ (* num-hidden 100.0) (+ num-hidden 
num-visible)) (if (= num-hidden 1) "was" "were"))))))))
-    (setq logview--applied-filters filters)))
+          (goto-char region-begin)
+          (let ((anchored (logview--match-current-entry)))
+            (when (and set-up-entries (or (not anchored) (> (point) 
region-begin)))
+              ;; Asked to set up entries, but the buffer begins with
+              ;; or completely consists of text we don't recognize.
+              (put-text-property 1 (if anchored (match-beginning 0) (1+ 
(buffer-size))) 'face      nil)
+              (put-text-property 1 (if anchored (match-beginning 0) (1+ 
(buffer-size))) 'invisible nil))
+            (when anchored
+              (let ((reporter           (when reporter-builder (funcall 
reporter-builder (match-beginning 0) region-end)))
+                    (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))
+                    (hider              (logview--hide-entry-callback 
'logview-filtered))
+                    (shower             (logview--show-entry-callback 
'logview-filtered))
+                    (explicit-shower    (when cancel-explicit-hiding 
(logview--show-entry-callback 'logview-hidden-entry)))
+                    (num-hidden         0)
+                    (num-visible        0)
+                    (match-data-storage '(nil))
+                    message-begin
+                    matches-name/thread
+                    level-data)
+                ;; Because 'callback' doesn't get access to match
+                ;; data, while 'validator' doesn't know all entry
+                ;; limits, we use both and pass 'matches-name/thread'
+                ;; from the validator to the callback.
+                (logview--iterate-entries-forward
+                 (lambda (begin after-first-line end)
+                   (when (and set-up-entries (< after-first-line end) 
have-level)
+                     (when have-level
+                       (put-text-property after-first-line end 'face (nth 1 
level-data)))
+                     (put-text-property (logview--linefeed-back 
after-first-line) (logview--linefeed-back end)
+                                        'invisible (list (nth 0 level-data) 
'logview-details)))
+                   ;; Remember that 'matches-name/thread' is not the
+                   ;; final value, we still need to check if entry's
+                   ;; message passes filters.
+                   (if (and matches-name/thread
+                            (or (null message-filter)
+                                ;; Ideally would just match in the
+                                ;; buffer itself, but that's probably
+                                ;; unsound due to anchors.
+                                (prog2
+                                    ;; For speed optimization we don't
+                                    ;; use 'save-match-data'.
+                                    (match-data t match-data-storage)
+                                    (let ((case-fold-search case-insensitive))
+                                      (funcall message-filter 
(buffer-substring-no-properties message-begin end)))
+                                  (set-match-data match-data-storage))))
+                       (unless set-up-entries
+                         (funcall shower begin after-first-line end)
+                         (setq num-visible (1+ num-visible)))
+                     (funcall hider begin after-first-line end)
+                     (setq num-hidden (1+ num-hidden)))
+                   (when explicit-shower
+                     (funcall explicit-shower begin after-first-line end))
+                   (when reporter
+                     (progress-reporter-update reporter end))
+                   ;; Continuing condition.
+                   (< end region-end))
+                 nil
+                 (lambda ()
+                   (let ((case-fold-search case-insensitive))
+                     (when set-up-entries
+                       (when have-level
+                         (setq level-data (cdr (assoc (match-string 
logview--level-group) logview--submode-level-data)))
+                         ;; Point is guaranteed to be at the start of the next 
line.
+                         (put-text-property (match-beginning 0) (point) 'face 
(nth 1 level-data))
+                         (put-text-property (logview--linefeed-back-checked 
(match-beginning 0)) (logview--linefeed-back (point))
+                                            'invisible (list (nth 0 
level-data)))
+                         (add-face-text-property (match-beginning 
logview--level-group)
+                                                 (match-end       
logview--level-group)
+                                                 (nth 2 level-data)))
+                       (when have-timestamp
+                         (add-face-text-property (match-beginning 
logview--timestamp-group)
+                                                 (match-end       
logview--timestamp-group)
+                                                 'logview-timestamp))
+                       (when have-name
+                         (add-face-text-property (match-beginning 
logview--name-group)
+                                                 (match-end       
logview--name-group)
+                                                 'logview-name))
+                       (when have-thread
+                         (add-face-text-property (match-beginning 
logview--thread-group)
+                                                 (match-end       
logview--thread-group)
+                                                 'logview-thread)))
+                     (setq message-begin       (match-end 0)
+                           matches-name/thread (or no-name/thread-filters
+                                                   ;; Since the filters 
involve regexp matching themselves,
+                                                   ;; we need to store log 
entry parts before calling any.
+                                                   (let ((name   (when 
name-filter   (match-string-no-properties logview--name-group)))
+                                                         (thread (when 
thread-filter (match-string-no-properties logview--thread-group))))
+                                                     (and (or (null name)   
(funcall name-filter   name))
+                                                          (or (null thread) 
(funcall thread-filter thread)))))))
+                   ;; Operate on all entries.
+                   t))
+                (if set-up-entries
+                    (when reporter
+                      (progress-reporter-done reporter))
+                  (cond ((= num-hidden 0)
+                         (message (if (equal filter-id 
logview--empty-filter-id) "Filters are reset" "Filtering complete, nothing was 
hidden")))
+                        ((= num-visible 0)
+                         (message "Filtering complete, all entries were 
hidden"))
+                        (t
+                         (message "Filtering complete, %d %s out of %d 
(%.1f%%) %s hidden"
+                                  num-hidden (if (= num-hidden 1) "entry" 
"entries") (+ num-hidden num-visible)
+                                  (/ (* num-hidden 100.0) (+ num-hidden 
num-visible)) (if (= num-hidden 1) "was" "were")))))))))
+      (setq logview--applied-filter-id filter-id)))))
 
 ;; FIXME: Resulting regexp will not be valid if any of the options
 ;;        uses group backreferences (\N) and maybe some other
 ;;        constructs.
 (defun logview--build-filter-regexp (options)
   (when options
-    (mapconcat 'identity options "\\|")))
+    ;; To prevent refiltering on insignificant changes, we enforce
+    ;; canonical option ordering and drop any duplicates.
+    (mapconcat 'identity (delete-consecutive-dups (sort options 'string<)) 
"\\|")))
 
 
 (defun logview--show-entry-callback (hider)
@@ -1621,74 +1669,10 @@ Optional third argument is to make the function 
suitable for
 `after-change-functions' and is ignored there.  Special value
 'report-progress for this argument is treated differently."
   (when logview--process-buffer-changes
-    (save-excursion
-      (save-match-data
-        (save-restriction
-          (with-silent-modifications
-            (widen)
-            (goto-char begin)
-            (forward-line 0)
-            (let ((inhibit-read-only t)
-                  (case-fold-search  nil)
-                  (anchored          t))
-              ;; Inlining `logview--match-successive-entries' for
-              ;; performance reasons.
-              (unless (or (looking-at logview--entry-regexp)
-                          (re-search-backward logview--entry-regexp nil t))
-                (let ((anchor (if (re-search-forward logview--entry-regexp nil 
t)
-                                  (match-beginning 0)
-                                (setq anchored nil)
-                                (point-max))))
-                  (put-text-property 1 anchor 'face      nil)
-                  (put-text-property 1 anchor 'invisible nil)))
-              (when anchored
-                (let* ((entry-begin    (match-beginning 0))
-                       (after-first-line)
-                       (level-data)
-                       (reporter       (when (eq old-length 'report-progress)
-                                         (make-progress-reporter "Parsing 
buffer..." entry-begin end entry-begin)))
-                       (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)))
-                  (while (progn
-                           (forward-line)
-                           (setq after-first-line (point))
-                           (when have-level
-                             (setq level-data (cdr (assoc (match-string 
logview--level-group) logview--submode-level-data)))
-                             (put-text-property entry-begin after-first-line 
'face (nth 1 level-data))
-                             (put-text-property 
(logview--linefeed-back-checked entry-begin) (logview--linefeed-back 
after-first-line)
-                                                'invisible (list (nth 0 
level-data)))
-                             (add-face-text-property (match-beginning 
logview--level-group)
-                                                     (match-end       
logview--level-group)
-                                                     (nth 2 level-data)))
-                           (when have-timestamp
-                             (add-face-text-property (match-beginning 
logview--timestamp-group)
-                                                     (match-end       
logview--timestamp-group)
-                                                     'logview-timestamp))
-                           (when have-name
-                             (add-face-text-property (match-beginning 
logview--name-group)
-                                                     (match-end       
logview--name-group)
-                                                     'logview-name))
-                           (when have-thread
-                             (add-face-text-property (match-beginning 
logview--thread-group)
-                                                     (match-end       
logview--thread-group)
-                                                     'logview-thread))
-                           (setq entry-begin (if (or (looking-at 
logview--entry-regexp)
-                                                     (re-search-forward 
logview--entry-regexp nil t))
-                                                 (match-beginning 0)
-                                               (point-max)))
-                           ;; Here 'entry-begin' is actually for the next 
entry.
-                           (when (< after-first-line entry-begin)
-                             (when have-level
-                               (put-text-property after-first-line entry-begin 
'face (nth 1 level-data)))
-                             (put-text-property (logview--linefeed-back 
after-first-line) (logview--linefeed-back entry-begin)
-                                                'invisible (list (nth 0 
level-data) 'logview-details)))
-                           (when reporter
-                             (progress-reporter-update reporter end))
-                           (< entry-begin end)))
-                  (when reporter
-                    (progress-reporter-done reporter)))))))))))
+    (save-match-data
+      (logview--process-region-entries begin end t nil
+                                       (when (eq old-length 'report-progress)
+                                         (lambda (begin end) 
(make-progress-reporter "Parsing buffer..." begin end)))))))
 
 (defun logview--buffer-substring-filter (begin end delete)
   "Optionally remove invisible text from the substring."
@@ -1752,7 +1736,7 @@ Optional third argument is to make the function suitable 
for
     (switch-to-buffer parent)
     (set-window-configuration windows)
     (when save
-      (setq logview--all-current-filters filters)
+      (setq logview--current-filter-text filters)
       (logview--parse-filters)
       (logview--apply-parsed-filters))))
 
@@ -1765,7 +1749,7 @@ Optional third argument is to make the function suitable 
for
     (insert "\n"))
   ;; Put cursor at the first filter beginning if possible.
   (goto-char 1)
-  (logview--iterate-filter-lines (lambda (type line-begin begin end)
+  (logview--iterate-filter-lines (lambda (type _line-begin begin _end)
                                    (if (member type 
logview--valid-filter-prefixes)
                                        (progn (goto-char begin) nil)
                                      t)))

Reply via email to