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

    Parse and filter log buffers lazily, as driven by standard Emacs font lock.
---
 TODO.md    |    6 -
 logview.el | 1237 ++++++++++++++++++++++++++++++------------------------------
 2 files changed, 613 insertions(+), 630 deletions(-)

diff --git a/TODO.md b/TODO.md
index 557e7ccba3..ebc133d26c 100644
--- a/TODO.md
+++ b/TODO.md
@@ -10,9 +10,6 @@
   is not so difficult to implement, but requires pondering on how to
   make the commands comfortable to use.
 
-* Idle buffer parsing/filtering, otherwise mode is semi-useless in
-  huge logs.
-
 * Undo/redo for various filtering and explicit hiding operations.
 
 * Context when filtering (like grep -C): optionally show N entries
@@ -33,6 +30,3 @@
 * 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
-
-* Maybe optionally highlight the current entry?  Though we already use
-  background color heavily.
diff --git a/logview.el b/logview.el
index 36d195af9f..fd080438e4 100644
--- a/logview.el
+++ b/logview.el
@@ -35,6 +35,21 @@
 
 ;;; Code:
 
+;; Internally, we cannot use the point for most purposes, since correct 
interpretation of
+;; `logview-entry' text property value heavily depends on knowing *exact* 
entry beginning.
+;; When moving point, Emacs always adjusts it so it doesn't fall inside an 
invisible
+;; range, which screws things up for us.  In the same vein, we always operate 
on
+;; temporarily widened buffer, because it is not even possible to query text 
properties
+;; outside of narrowed-to region.
+;;
+;; While the above sounds like potential problems only for the case someone 
hides half of
+;; an entry or narrows from the middle of an entry, it really isn't.  I have 
experienced
+;; bugs even in normal testing with entries fully hidden or shown only.
+;;
+;; In short, use point (e.g. `goto-char') only when delivering results of 
internal
+;; computations to the user.
+
+
 (eval-when-compile (require 'cl-lib)
                    (require 'help-mode))
 (require 'datetime)
@@ -475,6 +490,7 @@ this face is used."
 
 ;;; Internal variables and constants.
 
+;; Keep in sync with `logview--entry-*' and `logview--find-region-entries'.
 (defconst logview--timestamp-group 1)
 (defconst logview--level-group     2)
 (defconst logview--name-group      3)
@@ -482,7 +498,8 @@ this face is used."
 (defconst logview--ignored-group   5)
 (defconst logview--message-group   6)
 
-(defconst logview--final-levels '(error warning information debug trace))
+(defconst logview--final-levels '(error warning information debug trace)
+  "List of final (submode-independent) levels, most to least severe.")
 
 (defconst logview--entry-part-regexp (rx bow (or (group "TIMESTAMP")
                                                  (group "LEVEL")
@@ -501,42 +518,37 @@ this face is used."
 
 (defvar logview--all-timestamp-formats-cache nil)
 
-(defconst logview--valid-text-filter-prefixes '("a+" "a-" "t+" "t-" "m+" "m-"))
-(defconst logview--valid-filter-prefixes      (append '("lv" "LV") 
logview--valid-text-filter-prefixes))
+(defconst logview--valid-filter-prefixes '("lv" "LV" "a+" "a-" "t+" "t-" "m+" 
"m-"))
+
 
+(defvar-local logview--point-min nil)
+(defvar-local logview--point-max nil)
 
 (defvar-local logview--submode-name nil)
 (defvar-local logview--entry-regexp nil)
 (defvar-local logview--submode-features nil)
 
-(defvar-local logview--submode-level-alist nil
-  "Submode levels mapped to final levels.
-Levels are ordered least to most important.")
-
 (defvar-local logview--submode-level-data nil
-  "An alist of level string to the following vectors:
-0: level invisibility symbol (for quick filtering);
-1: level invisibility symbol when filtered (for quick filtering).
-2: level entry face;
-3: level string face;
-4: result of `logview--hide-entry-callback' with item 1;
-5: result of `logview--show-entry-callback' with item 1.")
-
-(defvar-local logview--min-shown-level        nil)
-(defvar-local logview--min-always-shown-level nil)
-(defvar-local logview--as-important-levels    nil)
-(defvar-local logview--hide-all-details       nil)
+  "An alist of level strings to (INDEX . (ENTRY-FACE . STRING-FACE)).")
 
-(defvar-local logview--current-filter-text "")
+(defvar-local logview--submode-level-faces nil
+  "A vector of (ENTRY-FACE . STRING-FACE).")
 
-;; 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-local logview--as-important-level nil)
+(defvar-local logview--hide-all-details   nil)
 
-(defvar logview--empty-filter-id '((nil nil) (nil nil) (nil nil)))
-(defvar-local logview--applied-filter-id logview--empty-filter-id)
+(defvar-local logview--current-filter-text "")
+(defvar-local logview--current-filter      nil)
+
+;; I also considered using private cons cells where we could reset `car' e.g. 
from
+;; `logview-filtered' to nil.  However, this is very shaky and will stop 
working if any
+;; minor mode tweaks `invisible' property the "wrong" way.  Another 
possibility would be
+;; to use uninterned symbols, but that would be very confusing, since in the 
output they
+;; would look the same.  Therefore, I decided to include "generation" in the 
symbol
+;; instead.
+(defvar-local logview--filtered-symbol       'logview-filtered/0)
+(defvar-local logview--hidden-entry-symbol   'logview-hidden-entry/0)
+(defvar-local logview--hidden-details-symbol 'logview-hidden-details/0)
 
 (defvar logview--submode-name-history     nil)
 (defvar logview--timestamp-format-history nil)
@@ -557,6 +569,10 @@ Levels are ordered least to most important.")
 (defvar-local logview--filter-editing-buffer nil)
 (defvar logview--view-editing-buffer         nil)
 
+;; Not too small to avoid calling `logview--fontify-region' and
+;; `logview--find-region-entries' often: calling and setup involves some 
overhead.
+(defvar logview--lazy-region-size 50000)
+
 
 (defvar-local logview-filter-edit--parent-buffer             nil)
 (defvar-local logview-filter-edit--window-configuration      nil)
@@ -654,21 +670,90 @@ Levels are ordered least to most important.")
 ;; Lisp is sensitive to declaration order, so these are collected at
 ;; the beginnig of the file.
 
-(defmacro logview--std-matching (&rest body)
-  (declare (indent 0) (debug t))
-  `(save-excursion
-     (let ((case-fold-search nil))
-       ,@body)))
-
-(defmacro logview--std-matching-and-altering (&rest body)
+(defmacro logview--std-altering (&rest body)
   (declare (indent 0) (debug t))
   `(save-excursion
      (let ((logview--process-buffer-changes nil)
-           (case-fold-search                nil)
            (inhibit-read-only               t))
        (with-silent-modifications
          ,@body))))
 
+(defmacro logview--std-temporarily-widening (&rest body)
+  (declare (indent 0) (debug t))
+  `(save-restriction
+     (let ((logview--point-min (logview--point-min))
+           (logview--point-max (logview--point-max)))
+       (widen)
+       ,@body)))
+
+(defmacro logview--locate-current-entry (entry start &rest body)
+  (declare (indent 2) (debug (symbolp symbolp body)))
+  (cond ((and entry start)
+         (let ((entry+start (make-symbol "$entry+start")))
+           `(let* ((,entry+start (logview--do-locate-current-entry))
+                   (,entry       (car ,entry+start))
+                   (,start       (cdr ,entry+start)))
+              ,@body)))
+        (entry
+         `(let ((,entry (car (logview--do-locate-current-entry))))
+            ,@body))
+        (start
+         `(let ((,start (cdr (logview--do-locate-current-entry))))
+            ,@body))
+        (t
+         `(progn (logview--do-locate-current-entry)
+                 ,@body))))
+
+
+(defsubst logview--point-min ()
+  (or logview--point-min (point-min)))
+
+(defsubst logview--point-max ()
+  (or logview--point-max (point-max)))
+
+(defsubst logview--widen ()
+  (setq logview--point-min (point-min)
+        logview--point-max (point-max)))
+
+
+;; Value of text property `logview-entry' is a vector with the following 
elements:
+;; - 0:   entry end offset, i.e. total entry length;
+;; - 1:   message start offset, i.e. entry header length;
+;; - 2-9: group 1-4 (timestamp, level, name, thread) begin/end offsets;
+;; - 10:  details (second line) start offset, or nil if there is no second 
line;
+;; - 11:  entry level as a number;
+;;
+;; Offsets are relative to the entry beginning.  We store offsets so that 
values remain
+;; valid even if buffer text is shifted forwards or backwards.
+;;
+;; For all the following functions, ENTRY must be a value of `logview-entry' 
property.
+(defsubst logview--entry-end (entry start)
+  (+ start (aref entry 0)))
+
+(defsubst logview--entry-message-start (entry start)
+  (+ start (aref entry 1)))
+
+(defsubst logview--entry-message (entry start)
+  (buffer-substring-no-properties (logview--entry-message-start entry start) 
(logview--entry-end entry start)))
+
+(defsubst logview--entry-group-start (entry start group)
+  (+ start (aref entry (* group 2))))
+
+(defsubst logview--entry-group-end (entry start group)
+  (+ start (aref entry (1+ (* group 2)))))
+
+(defsubst logview--entry-group (entry start group)
+  (let ((base (* group 2)))
+    (buffer-substring-no-properties (+ start (aref entry base)) (+ start (aref 
entry (1+ base))))))
+
+(defsubst logview--entry-details-start (entry start)
+  (let ((details-offset (aref entry 10)))
+    (when details-offset
+      (+ start details-offset))))
+
+(defsubst logview--entry-level (entry)
+  (aref entry 11))
+
 
 ;; The following (inlined) functions are needed when applying
 ;; 'invisible' property.  Generally we count entry from start of its
@@ -809,10 +894,12 @@ successfully.")
 (define-derived-mode logview-mode nil "Logview"
   "Major mode for viewing and filtering various log files."
   (logview--update-keymap)
-  (add-hook 'read-only-mode-hook 'logview--update-keymap nil t)
-  (set (make-local-variable 'filter-buffer-substring-function) 
'logview--buffer-substring-filter)
-  (set (make-local-variable 'isearch-filter-predicate)         
'logview--isearch-filter-predicate)
-  (add-hook 'change-major-mode-hook 'logview--exiting-mode nil t)
+  (add-hook 'read-only-mode-hook #'logview--update-keymap nil t)
+  (setq font-lock-defaults '(nil))
+  (set (make-local-variable 'font-lock-fontify-region-function) 
#'logview--fontify-region)
+  (set (make-local-variable 'filter-buffer-substring-function)  
#'logview--buffer-substring-filter)
+  (set (make-local-variable 'isearch-filter-predicate)          
#'logview--isearch-filter-predicate)
+  (add-hook 'change-major-mode-hook #'logview--exiting-mode nil t)
   (logview--guess-submode)
   (logview--update-invisibility-spec)
   (unless (logview-initialized-p)
@@ -824,11 +911,9 @@ successfully.")
                    logview-mode-inactive-map)))
 
 (defun logview--exiting-mode ()
-  ;; Remove custom invisibility property values, as otherwise other
-  ;; modes will show empty buffers.  Also remove face property, as we
-  ;; set it ourselves, not through font-lock.
-  (logview--std-matching-and-altering
-    (remove-text-properties 1 (1+ (buffer-size)) '(face nil invisible nil))))
+  (logview--std-temporarily-widening
+    (logview--std-altering
+      (remove-text-properties (point-min) (point-max) '(invisible nil 
logview-entry nil)))))
 
 (defun logview-initialized-p ()
   (not (null logview--entry-regexp)))
@@ -845,17 +930,13 @@ message, which is especially useful for multiline 
messages.  In
 Transient Mark mode also activate the region."
   (interactive "P")
   (logview--assert)
-  (let ((case-fold-search nil))
-    (when (logview--match-current-entry)
-      (goto-char (match-end 0))
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry start
+      (goto-char (logview--entry-message-start entry start))
       (when select-message
-        (save-excursion
-          (push-mark (logview--linefeed-back (if (equal 
(logview--match-successive-entries 1) 0)
-                                                 (match-beginning 0)
-                                               (point-max)))
-                     t t)))
-      (unless (and select-message transient-mark-mode)
-        (logview--maybe-pulse-current-entry 'message-beginning)))))
+        (push-mark (logview--entry-end entry start))))
+    (unless (and select-message transient-mark-mode)
+      (logview--maybe-pulse-current-entry 'message-beginning))))
 
 (defun logview-next-entry (&optional n)
   "Move point vertically down N (1 by default) log entries.
@@ -868,11 +949,8 @@ the function will have significantly different effect."
   (logview--assert)
   (unless n
     (setq n 1))
-  (when (/= n 0)
-    (let ((case-fold-search nil)
-          (original-point   (point))
-          (remaining        (logview--match-successive-entries n t)))
-      (goto-char (if remaining (match-end 0) original-point))
+  (logview--std-temporarily-widening
+    (let ((remaining (logview--forward-entry n nil)))
       (logview--maybe-pulse-current-entry 'movement)
       (logview--maybe-complain-about-movement n remaining))))
 
@@ -903,25 +981,14 @@ resulting entry."
   (logview--assert 'level)
   (unless n
     (setq n 1))
-  (when (/= n 0)
-    (let ((case-fold-search nil)
-          (original-point   (point)))
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry nil
       (unless (memq last-command '(logview-next-as-important-entry 
logview-previous-as-important-entry))
-        (setq logview--as-important-levels nil)
-        (logview--match-current-entry)
-        (let ((this-level (match-string logview--level-group))
-              (found))
-          (dolist (level-pair logview--submode-level-alist)
-            (unless found
-              (setq found (equal (car level-pair) this-level)))
-            (when found
-              (push (car level-pair) logview--as-important-levels)))))
-      (let ((remaining (logview--match-successive-entries
-                        n t (lambda ()
-                              (member (match-string logview--level-group) 
logview--as-important-levels)))))
-        (goto-char (if remaining (match-end 0) original-point))
-        (logview--maybe-pulse-current-entry 'movement)
-        (logview--maybe-complain-about-movement n remaining 'as-important)))))
+        (setq logview--as-important-level (logview--entry-level entry))))
+    (let* ((level     (or logview--as-important-level most-positive-fixnum))
+           (remaining (logview--forward-entry n (lambda (entry _start) (<= 
(logview--entry-level entry) level)))))
+      (logview--maybe-pulse-current-entry 'movement)
+      (logview--maybe-complain-about-movement n remaining 'as-important))))
 
 (defun logview-previous-as-important-entry (&optional n)
   "Move point vertically up N 'as important' entries.
@@ -954,62 +1021,9 @@ command 
`\\<logview-mode-map>\\[logview-set-navigation-view]' to change that lat
                                           "Navigate through view (change with 
`\\[logview-set-navigation-view]'): "))))))
   (unless n
     (setq n 1))
-  (when (/= n 0)
-    (let* (;; Need a copy, since entry matching is always case-sensitive.
-           (case-insensitive case-fold-search)
-           (case-fold-search nil)
-           (original-point   (point))
-           (remaining
-            (logview--do-parse-filters
-             (plist-get (logview--find-view logview--navigation-view-name) 
:filters) nil
-             (lambda (min-shown-level min-always-shown-level 
_canonical-filter-text
-                                      name-filter thread-filter message-filter)
-               (let ((name-filter            (car name-filter))
-                     (thread-filter          (car thread-filter))
-                     (message-filter         (car message-filter))
-                     (no-name/thread-filters (and (null name-filter) (null 
thread-filter)))
-                     (show                   (+ (if logview--min-shown-level 0 
1) (if min-shown-level 0 1)))
-                     (always-show            nil)
-                     valid-levels
-                     always-matching-levels)
-                 (dolist (level-pair logview--submode-level-data)
-                   ;; Performance optimization: don't include levels that
-                   ;; are filtered off into `valid-levels'.
-                   (when (equal (car level-pair) logview--min-shown-level)
-                     (setq show (1+ show)))
-                   (when (equal (car level-pair) min-shown-level)
-                     (setq show (1+ show)))
-                   (when (equal (car level-pair) min-always-shown-level)
-                     (setq always-show t))
-                   (if always-show
-                       (push (car level-pair) always-matching-levels)
-                     (when (= show 2)
-                       (push (car level-pair) valid-levels))))
-                 (logview--match-successive-entries
-                  n t (lambda ()
-                        (let ((level (match-string-no-properties 
logview--level-group)))
-                          (or (member level always-matching-levels)
-                              (and (member level valid-levels)
-                                   ;; Since the filters involve regexp 
matching themselves,
-                                   ;; we need to store log entry parts before 
calling any.
-                                   (let ((message-begin      (match-end 0))
-                                         (match-data-storage '(nil)))
-                                     (match-data t match-data-storage)
-                                     (prog1 (and (or no-name/thread-filters
-                                                     (let ((case-fold-search 
case-insensitive)
-                                                           (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)))))
-                                                 (or (null message-filter)
-                                                     (let ((case-fold-search 
case-insensitive)
-                                                           (message-end      
(save-excursion
-                                                                               
(if (equal (logview--match-successive-entries 1) 0)
-                                                                               
    (logview--linefeed-back (match-beginning 0))
-                                                                               
  (buffer-size)))))
-                                                       (funcall message-filter 
(buffer-substring-no-properties message-begin message-end)))))
-                                       (set-match-data 
match-data-storage)))))))))))))
-      (goto-char (if remaining (match-end 0) original-point))
+  (logview--std-temporarily-widening
+    (let* ((filters   (plist-get (logview--find-view 
logview--navigation-view-name) :filters))
+           (remaining (logview--forward-entry n (cdr 
(logview--do-parse-filters filters)))))
       (logview--maybe-pulse-current-entry 'navigation-view)
       (logview--maybe-complain-about-movement n remaining 
logview--navigation-view-name))))
 
@@ -1030,14 +1044,7 @@ command 
`\\<logview-mode-map>\\[logview-set-navigation-view]' to change that lat
 Point is positioned at the beginning of the message of the entry.
 Otherwise this function is similar to `beginning-of-buffer'."
   (interactive)
-  (logview--assert)
-  (unless (region-active-p)
-    (push-mark))
-  (goto-char (point-min))
-  (let ((case-fold-search nil))
-    (when (logview--match-current-entry)
-      (goto-char (match-end 0))
-      (logview--maybe-pulse-current-entry 'movement))))
+  (logview--do-relocate #'logview--point-min))
 
 (defun logview-last-entry ()
   "Move point to the last log entry.
@@ -1046,14 +1053,17 @@ Point is positioned at the beginning of the message of 
the entry.
 If the last entry is multiline, this makes the function quite
 different from `end-of-buffer'."
   (interactive)
+  (logview--do-relocate #'logview--point-max))
+
+(defun logview--do-relocate (where-provider)
   (logview--assert)
   (unless (region-active-p)
     (push-mark))
-  (goto-char (point-max))
-  (let ((case-fold-search nil))
-    (when (logview--match-current-entry)
-      (goto-char (match-end 0))
-      (logview--maybe-pulse-current-entry 'movement))))
+  (logview--std-temporarily-widening
+    (goto-char (funcall where-provider))
+    (logview--locate-current-entry entry start
+      (goto-char (logview--entry-message-start entry start)))
+    (logview--maybe-pulse-current-entry 'movement)))
 
 
 
@@ -1086,13 +1096,16 @@ prefix means zero."
   (let ((from (point-min))
         (to   (point-max)))
     (widen)
-    (logview--std-matching
-      (narrow-to-region (if (and upwards (equal 
(logview--match-successive-entries (if n (- n) 0) t) 0))
-                            (match-beginning 0)
-                          from)
-                        (if (and (not upwards) (equal 
(logview--match-successive-entries (if n (1+ n) 1) t) 0))
-                            (match-beginning 0)
-                          to)))))
+    (narrow-to-region (if upwards
+                          (progn (logview--forward-entry (if n (- n) 0))
+                                 (logview--locate-current-entry nil start
+                                   (max start from)))
+                        from)
+                      (if upwards
+                          to
+                        (logview--forward-entry (or n 0))
+                        (logview--locate-current-entry entry start
+                          (min (logview--entry-end entry start) to))))))
 
 (defun logview-widen-upwards ()
   "Widen the buffer only upwards, i.e. keep the bottom restriction."
@@ -1149,9 +1162,9 @@ warning, all entries other than warnings and errors will 
be
 hidden."
   (interactive)
   (logview--assert 'level)
-  (logview--std-matching
-    (when (logview--match-current-entry)
-      (logview--change-min-level-filter (match-string logview--level-group)))))
+  (logview--std-temporarily-widening
+    (logview--locate-current-entry entry nil
+      (logview--change-min-level-filter (car (nth (- (length 
logview--submode-level-data) 1 (logview--entry-level entry)) 
logview--submode-level-data))))))
 
 (defun logview-always-show-errors ()
   "Always show error entries."
@@ -1185,15 +1198,15 @@ match the current text filters."
 (defun logview--find-min-level (final-level)
   "Find minimal submode level that maps to given FINAL-LEVEL or higher."
   (logview--assert 'level)
-  (let ((result)
-        (final-level-index (cl-position final-level logview--final-levels)))
-    (dolist (level-pair logview--submode-level-alist)
-      (when (and (null result) (<= (cl-position (cdr level-pair) 
logview--final-levels :test 'equal) final-level-index))
-        (setq result (car level-pair))))
-    result))
+  (let ((lower-level-faces (mapcar (lambda (final-level) (intern (format 
"logview-%s-entry" (symbol-name final-level))))
+                                   (cdr (memq final-level 
logview--final-levels)))))
+    (catch 'level
+      (dolist (level-data logview--submode-level-data)
+        (unless (memq (cadr (cdr level-data)) lower-level-faces)
+          (throw 'level (car level-data)))))))
 
 (defun logview--change-min-level-filter (min-level &optional always-show)
-  (when (and min-level (string= min-level (caar logview--submode-level-alist)))
+  (when (and min-level (string= min-level (caar logview--submode-level-data)))
     (setq min-level nil))
   (let ((case-fold-search nil)
         (filter-prefix    (if always-show "LV" "lv")))
@@ -1212,12 +1225,6 @@ match the current text filters."
                                                    (substring 
logview--current-filter-text level-filter-at))))))
   (logview--parse-filters))
 
-(defun logview--set-min-level (min-level min-always-shown-level)
-  (unless (and (equal logview--min-shown-level min-level) (equal 
logview--min-always-shown-level min-always-shown-level))
-    (setq logview--min-shown-level        min-level
-          logview--min-always-shown-level min-always-shown-level)
-    (logview--update-invisibility-spec)))
-
 
 
 ;;; Filtering by name/thread commands.
@@ -1289,10 +1296,11 @@ that doesn't match any of entered expression."
 (defun logview--prompt-for-new-filter (prompt type filter-line-prefix)
   (logview--assert type)
   (let* ((default-value (unless (eq type 'message)
-                          (logview--std-matching
-                            (when (logview--match-current-entry)
-                              (let ((base (regexp-quote (match-string (cdr 
(assq type (list (cons 'name   logview--name-group)
-                                                                               
             (cons 'thread logview--thread-group))))))))
+                          (logview--std-temporarily-widening
+                            (logview--locate-current-entry entry start
+                              (let ((base (regexp-quote (logview--entry-group 
entry start (pcase-exhaustive type
+                                                                               
             (`name   logview--name-group)
+                                                                               
             (`thread logview--thread-group))))))
                                 (list base (format "^%s$" base)))))))
          (regexp        (read-regexp prompt default-value (cdr (assq type 
'((name    . logview--name-regexp-history)
                                                                             
(thread  . logview--thread-regexp-history)
@@ -1306,8 +1314,7 @@ that doesn't match any of entered expression."
                                                           (not 
(string-suffix-p "\n" logview--current-filter-text)))
                                                  "\n")
                                                filter-line-prefix " " 
(replace-regexp-in-string "\n" "\n.. " regexp) "\n"))
-    (logview--parse-filters)
-    (logview--apply-parsed-filters)))
+    (logview--parse-filters)))
 
 ;; This must have been a standard function.
 (defun logview--valid-regexp-p (regexp)
@@ -1332,22 +1339,19 @@ level to show entries regardless of text filters."
   "Reset all name filters."
   (interactive)
   (logview--assert 'name)
-  (logview--parse-filters '("a+" "a-"))
-  (logview--apply-parsed-filters))
+  (logview--parse-filters '("a+" "a-")))
 
 (defun logview-reset-thread-filters ()
   "Reset all thread filters."
   (interactive)
   (logview--assert 'thread)
-  (logview--parse-filters '("t+" "t-"))
-  (logview--apply-parsed-filters))
+  (logview--parse-filters '("t+" "t-")))
 
 (defun logview-reset-message-filters ()
   "Reset all message filters."
   (interactive)
   (logview--assert)
-  (logview--parse-filters '("m+" "m-"))
-  (logview--apply-parsed-filters))
+  (logview--parse-filters '("m+" "m-")))
 
 (defun logview-reset-all-filters ()
   "Reset all filters (level, name, thread).
@@ -1368,13 +1372,11 @@ entries and cancel any narrowing restrictions."
   (logview--assert)
   (when also-show-details
     (setq logview--hide-all-details nil))
-  (if (memq 'level logview--submode-features)
-      (logview-reset-level-filters)
-    (when also-show-details
-      (logview--update-invisibility-spec)))
-  (when (or (memq 'name logview--submode-features) (memq 'thread 
logview--submode-features) also-cancel-explicit-hiding)
-    (logview--parse-filters logview--valid-text-filter-prefixes)
-    (logview--apply-parsed-filters also-cancel-explicit-hiding)))
+  (when also-cancel-explicit-hiding
+    (logview--retire-hiding-symbol 'logview--hidden-entry-symbol)
+    (logview--retire-hiding-symbol 'logview--hidden-details-symbol))
+  (unless (logview--parse-filters logview--valid-filter-prefixes)
+    (logview--update-invisibility-spec)))
 
 
 
@@ -1385,8 +1387,7 @@ entries and cancel any narrowing restrictions."
 Interactively, read the view name from the minibuffer."
   (interactive (list (logview--choose-view "Switch to view: ")))
   (setq logview--current-filter-text (plist-get (logview--find-view view) 
:filters))
-  (logview--parse-filters)
-  (logview--apply-parsed-filters))
+  (logview--parse-filters))
 
 (defun logview-set-navigation-view (view)
   "Set a view to be used for navigation.
@@ -1439,7 +1440,7 @@ Interactively, read the view name from the minibuffer."
     (logview--completing-read prompt defined-names nil t nil 
'logview--view-name-history)))
 
 (defun logview--do-save-filters-as-view (name global)
-  (unless (or logview--min-shown-level (car logview--name-filter) (car 
logview--thread-filter) (car logview--message-filter))
+  (unless (car logview--current-filter)
     (user-error "There are currently no filters"))
   (unless name
     (setq name (read-string "Save as: " nil 'logview--view-name-history)))
@@ -1495,7 +1496,7 @@ Interactively, read the view name from the minibuffer."
 
 ;;; Explicit entry hiding/showing commands.
 
-(defun logview-hide-entry (&optional n)
+(defun logview-hide-entry (&optional n interactive)
   "Explicitly hide N currently visible entries starting at point.
 If N is negative, hide -N previous entries instead, not including
 the current.
@@ -1505,17 +1506,18 @@ is invoked without prefix argument, hide all entries in 
the
 region instead (i.e. just like `logview-hide-region-entries')."
   (interactive (list (if (or current-prefix-arg (not (use-region-p)))
                          (prefix-numeric-value current-prefix-arg)
-                       'use-region)))
+                       'use-region)
+                     t))
   (if (eq n 'use-region)
-      (logview-hide-region-entries (point) (mark))
+      (logview-hide-region-entries (point) (mark) interactive)
     (logview--assert)
     (unless n
       (setq n 1))
-    (logview--std-matching-and-altering
-      (logview--maybe-complain-about-movement
-       n (logview--iterate-successive-entries n (logview--hide-entry-callback 
'logview-hidden-entry) t)))))
+    (logview--std-temporarily-widening
+      (logview--std-altering
+        (logview--maybe-complain-about-movement n 
(logview--iterate-successive-entries (point) n #'logview--hide-entry-callback 
t))))))
 
-(defun logview-hide-region-entries (begin end)
+(defun logview-hide-region-entries (begin end &optional interactive)
   "Explicitly hide all log entries in the region.
 Entries that are in the region only partially are hidden as well.
 
@@ -1523,12 +1525,15 @@ Note that this includes entries that are currently 
hidden due to
 filtering too.  If you later cancel filtering, all entries in the
 region will remain hidden until you also cancel the explicit
 hiding."
-  (interactive "r")
+  (interactive "r\np")
   (logview--assert)
-  (logview--std-matching-and-altering
-    (logview--iterate-entries-in-region begin end 
(logview--hide-entry-callback 'logview-hidden-entry))))
+  (logview--std-temporarily-widening
+    (logview--std-altering
+      (logview--iterate-entries-in-region begin end 
#'logview--hide-entry-callback)
+      (when interactive
+        (setq deactivate-mark t)))))
 
-(defun logview-show-entries (&optional n)
+(defun logview-show-entries (&optional n interactive)
   "Show explicitly hidden entries.
 By default, explicitly hidden entries between the current and the
 next visible are shown.  If invoked with prefix argument, entries
@@ -1543,45 +1548,53 @@ entries in the region instead (i.e. work just like
                          (prefix-numeric-value current-prefix-arg)
                        'use-region)))
   (if (eq n 'use-region)
-      (logview-show-region-entries (point) (mark))
+      (logview-show-region-entries (point) (mark) interactive)
     (logview--assert)
     (unless n
       (setq n 1))
-    ;; Much like 'logview--iterate-successive-entries', but because of
-    ;; peculiar semantics, not broken out into its own function.
-    (when (/= n 0)
-      (logview--std-matching-and-altering
-        (let ((direction (cl-signum n))
-              (shower    (logview--show-entry-callback 'logview-hidden-entry)))
-          (funcall (if (< n 0)
-                       'logview--iterate-entries-backward
-                     ;; To "not count" the current entry.
-                     (setq n (1+ n))
-                     'logview--iterate-entries-forward)
-                   (lambda (begin after-first-line entry-end)
-                     (if (invisible-p begin)
-                         (progn
-                           (funcall shower begin after-first-line entry-end)
-                           t)
-                       (/= (setq n (- n direction)) 0)))))))
-    (logview--maybe-complain-about-movement n n)))
-
-(defun logview-show-region-entries (begin end)
+    (logview--std-temporarily-widening
+      (logview--std-altering
+        ;; Much like 'logview--iterate-successive-entries', but because of
+        ;; peculiar semantics, not broken out into its own function.
+        (when (/= n 0)
+          (let ((direction (cl-signum n)))
+            (funcall (if (> n 0) #'logview--iterate-entries-forward 
#'logview--iterate-entries-backward)
+                     (point)
+                     (lambda (entry entry-at)
+                       (if (invisible-p entry-at)
+                           (progn (logview--show-entry-callback entry entry-at)
+                                  t)
+                         (/= (setq n (- n direction)) 0)))
+                     nil nil t))
+          (logview--maybe-complain-about-movement n n))))))
+
+(defun logview-show-region-entries (begin end &optional interactive)
   "Explicitly show all log entries in the region.
 
 Note that entries that are currently hidden due to filtering are
 also marked as 'not explicitly hidden'.  However, you will see
 any effect only once you clear or alter the responsible filters."
-  (interactive "r")
+  (interactive "r\np")
   (logview--assert)
-  (logview--std-matching-and-altering
-    (logview--iterate-entries-in-region begin end 
(logview--show-entry-callback 'logview-hidden-entry))))
+  (logview--std-temporarily-widening
+    (logview--std-altering
+      (logview--iterate-entries-in-region begin end 
#'logview--show-entry-callback)
+      (when interactive
+        (setq deactivate-mark t)))))
+
+(defun logview--hide-entry-callback (entry start)
+  (logview--update-entry-invisibility start (logview--entry-details-start 
entry start) (logview--entry-end entry start)
+                                      'propagate t 'propagate))
+
+(defun logview--show-entry-callback (entry start)
+  (logview--update-entry-invisibility start (logview--entry-details-start 
entry start) (logview--entry-end entry start)
+                                      'propagate nil 'propagate))
 
 
 
 ;;; Showing/hiding entry details commands.
 
-(defun logview-toggle-entry-details (&optional arg)
+(defun logview-toggle-entry-details (&optional arg interactive)
   "Toggle whether details for current entry are shown.
 If invoked with prefix argument, show them if the argument is
 positive, hide otherwise.
@@ -1591,65 +1604,48 @@ In Transient Mark mode, if the region is active, call
 for how toggling works."
   (interactive (list (if (use-region-p)
                          (list (or current-prefix-arg 'toggle))
-                       (or current-prefix-arg 'toggle))))
+                       (or current-prefix-arg 'toggle))
+                     t))
   (if (consp arg)
-      (logview-toggle-region-entry-details (point) (mark) (car arg))
-    (save-excursion
-      (save-restriction
-        (widen)
-        (logview--std-matching-and-altering
-          (when (logview--match-current-entry)
-            (forward-line)
-            (let ((after-first-line (point))
-                  (end              (progn (backward-char)
-                                           (if 
(logview--match-successive-entries 1)
-                                               (match-beginning 0)
-                                             (point-max)))))
-              (if (<= end after-first-line)
-                  (user-error "Current entry has no details")
-                (logview--change-entry-details-visibility after-first-line end
-                                                          (if (eq arg 'toggle)
-                                                              (memq 
'logview-hidden-details
-                                                                    
(get-text-property (logview--linefeed-back after-first-line) 'invisible))
-                                                            (> 
(prefix-numeric-value arg) 0)))))))))))
-
-(defun logview-toggle-region-entry-details (begin end &optional arg)
+      (logview-toggle-region-entry-details (point) (mark) (car arg) 
interactive)
+    (logview--std-temporarily-widening
+      (logview--std-altering
+        (logview--locate-current-entry entry start
+          (let ((details-start (logview--entry-details-start entry start)))
+            (unless details-start
+              (user-error "Current entry has no details"))
+            (logview--update-entry-invisibility start details-start 
(logview--entry-end entry start)
+                                                'propagate 'propagate (if (eq 
arg 'toggle)
+                                                                          (not 
(memq logview--hidden-details-symbol (get-text-property details-start 
'invisible)))
+                                                                        (> 
(prefix-numeric-value arg 0))))))))))
+
+(defun logview-toggle-region-entry-details (begin end &optional arg 
interactive)
   "Toggle whether details in the region are shown.
 Toggling works like this: if at least one entry in the region has
-details that are visible, all are hidden.  Otherwise, if all are
-already hidden, they are shown.  If invoked with prefix argument,
-show details if the argument is positive, hide otherwise.
+visible details, all are hidden.  Otherwise, if all are already
+hidden, they are shown.  If invoked with prefix argument, show
+details if the argument is positive, hide otherwise.
 
 Entries that are in the region only partially are operated on as
 well."
-  (interactive (list (point) (mark) (or current-prefix-arg 'toggle)))
-  (save-excursion
-    (save-restriction
-      (widen)
-      (logview--std-matching-and-altering
-        (when (eq arg 'toggle)
-          (setq arg 1)
-          (logview--iterate-entries-in-region begin end (lambda (_begin 
after-first-line end)
-                                                          (if (or (>= 
after-first-line end)
-                                                                  (memq 
'logview-hidden-details (get-text-property after-first-line 'invisible)))
+  (interactive (list (point) (mark) (or current-prefix-arg 'toggle) t))
+  (logview--std-temporarily-widening
+    (logview--std-altering
+      (when (eq arg 'toggle)
+        (setq arg 1)
+        (logview--iterate-entries-in-region begin end (lambda (entry start)
+                                                        (let ((details-start 
(logview--entry-details-start entry start)))
+                                                          (if (or (null 
details-start)
+                                                                  (memq 
logview--hidden-details-symbol (get-text-property details-start 'invisible)))
                                                               t
                                                             (setq arg 0)
-                                                            nil))))
-        (let ((show (> (prefix-numeric-value arg) 0)))
-          (logview--iterate-entries-in-region begin end (lambda (_begin 
after-first-line end)
-                                                          
(logview--change-entry-details-visibility after-first-line end show)
-                                                          t)))))))
-
-(defun logview--change-entry-details-visibility (after-first-line end show)
-  (let* ((current-invisible (get-text-property (logview--linefeed-back 
after-first-line) 'invisible))
-         (new-invisible     current-invisible))
-    (if show
-        (setq new-invisible (remq 'logview-hidden-details new-invisible))
-      (unless (memq 'logview-hidden-details new-invisible)
-        (push 'logview-hidden-details new-invisible)))
-    (unless (eq new-invisible current-invisible)
-      (put-text-property (logview--linefeed-back after-first-line) 
(logview--linefeed-back end) 'invisible new-invisible))))
-
+                                                            nil)))))
+      (let ((hide (<= (prefix-numeric-value arg) 0)))
+        (logview--iterate-entries-in-region begin end (lambda (entry start)
+                                                        
(logview--update-entry-invisibility start (logview--entry-details-start entry 
start) (logview--entry-end entry start)
+                                                                               
             'propagate 'propagate hide))))
+      (when interactive
+        (setq deactivate-mark t)))))
 
 (defun logview-toggle-details-globally (&optional arg)
   "Toggle whether details are shown in the whole buffer.
@@ -1761,7 +1757,9 @@ These are:
 
 (defun logview-pulse-current-entry ()
   (interactive)
-  (logview--maybe-pulse-current-entry))
+  (logview--assert)
+  (logview--std-temporarily-widening
+    (logview--maybe-pulse-current-entry)))
 
 (defun logview-mode-help ()
   (interactive)
@@ -2052,33 +2050,31 @@ returns non-nil."
           (when (or timestamp-regexp (null timestamp-at))
             (when timestamp-at
               (setcar timestamp-at (format "\\(?%d:%s\\)" 
logview--timestamp-group timestamp-regexp)))
-            (let ((regexp (apply #'concat parts)))
+            (let ((regexp      (apply #'concat parts))
+                  (level-index 0))
               (when (or (null test-line) (string-match regexp test-line))
                 (setq logview--submode-name           name
                       logview--process-buffer-changes t
                       logview--entry-regexp           regexp
                       logview--submode-features       features
-                      logview--submode-level-alist    nil)
+                      logview--submode-level-data     nil)
                 (logview--update-mode-name)
                 (when (memq 'level features)
                   (dolist (final-level logview--final-levels)
                     (dolist (level (cdr (assoc final-level levels)))
-                      (setq logview--submode-level-alist (cons (cons level 
final-level) logview--submode-level-alist))
-                      (let ((filtered-invisibility-spec (make-symbol (format 
"logview-filtered:%s" level))))
-                        (push (cons level (vector (make-symbol (format 
"logview:%s" level))
-                                                  filtered-invisibility-spec
-                                                  (intern (format 
"logview-%s-entry" (symbol-name final-level)))
-                                                  (intern (format 
"logview-level-%s" (symbol-name final-level)))
-                                                  
(logview--hide-entry-callback filtered-invisibility-spec)
-                                                  
(logview--show-entry-callback filtered-invisibility-spec)))
-                              logview--submode-level-data)))))
-                (logview--split-region-into-entries (point-min) (point-max) 
'report-progress)
-                (add-hook 'after-change-functions 
'logview--split-region-into-entries t t)
+                      (push (cons level (cons level-index (cons (intern 
(format "logview-%s-entry" (symbol-name final-level)))
+                                                                (intern 
(format "logview-level-%s" (symbol-name final-level))))))
+                            logview--submode-level-data)
+                      (setq level-index (1+ level-index)))))
+                (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)))
                 (read-only-mode 1)
                 (when buffer-file-name
                   (pcase logview-auto-revert-mode
                     (`auto-revert-mode      (auto-revert-mode      1))
                     (`auto-revert-tail-mode (auto-revert-tail-mode 1))))
+                (logview--refilter)
                 (throw 'success nil)))))))))
 
 (defun logview--all-timestamp-formats ()
@@ -2137,6 +2133,27 @@ returns non-nil."
                                          (thread . "Log doesn't include thread 
names"))))))))
 
 
+(defun logview--forward-entry (&optional n validator)
+  (logview--locate-current-entry last-valid-entry last-valid-start
+    (cond ((> n 0)
+           (logview--iterate-entries-forward (point)
+                                             (lambda (entry start)
+                                               (> (setq last-valid-entry entry
+                                                        last-valid-start start
+                                                        n                (1- 
n))
+                                                  0))
+                                             t validator t))
+          ((< n 0)
+           (logview--iterate-entries-backward (point)
+                                              (lambda (entry start)
+                                                (< (setq last-valid-entry entry
+                                                         last-valid-start start
+                                                         n                (1+ 
n))
+                                                   0))
+                                              t validator t)))
+    (goto-char (logview--entry-message-start last-valid-entry 
last-valid-start)))
+  n)
+
 (defun logview--maybe-complain-about-movement (direction remaining &optional 
type)
   ;; Using 'equal' since 'remaining' may also be nil.
   (unless (equal remaining 0)
@@ -2147,135 +2164,111 @@ returns non-nil."
                 type)))
 
 
-(defun logview--match-current-entry ()
-  "Match the header of the log entry where the point currently is.
-
-Return value is non-nil on success.  Point is either before or
-after the header, i.e. still in the same log entry, but there are
-no more guarantees.  Match data is set appropriately for the
-header."
-  (forward-line 0)
-  (or (looking-at logview--entry-regexp)
-      (re-search-backward logview--entry-regexp nil t)
-      (re-search-forward  logview--entry-regexp nil t)))
-
-(defun logview--match-successive-entries (n &optional only-visible validator)
-  "Match N entries after (if N is positive) or before (negative)
-the current one.  If N is zero, match just the current entry.
-
-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.
-
-Returns the remaining number, i.e. zero if there are enough valid
-entries.  If it never found any valid entries, returns nil.
-There is no guarantees about point location after the call, but
-match data will be set for the last valid matched header."
-  (let* ((forward          (> n 0))
-         (direction        (cl-signum n))
-         (successful-match (list nil)))
-    (when (logview--match-current-entry)
-      (when (or (null validator) (funcall validator))
-        (match-data t successful-match))
-      (when (/= n 0)
-        (while (and (= (forward-line direction) 0)
-                    (if forward
-                        (re-search-forward logview--entry-regexp nil t)
-                      (or (looking-at logview--entry-regexp)
-                          (re-search-backward logview--entry-regexp nil t)))
-                    (if (or (and only-visible (invisible-p (match-beginning 
0)))
-                            (and validator (not (funcall validator))))
-                        t
-                      (match-data t successful-match)
-                      (/= (setq n (- n direction)) 0))))))
-    (if (equal successful-match '(nil))
-        nil
-      (set-match-data successful-match)
-      n)))
-
-
-(defun logview--iterate-entries-forward (callback &optional only-visible 
validator)
+(defun logview--do-locate-current-entry (&optional position)
+  "Return the entry around POSITION and its beginning.
+If POSITION is nil, take the current value of point as the
+position, and also signal a user-level error if no entries can be
+located."
+  (let* ((entry-at (or position (point)))
+         (entry    (or (get-text-property entry-at 'logview-entry)
+                       (progn (logview--find-region-entries entry-at (+ 
entry-at logview--lazy-region-size))
+                              (get-text-property entry-at 'logview-entry)))))
+    (if entry
+        (when (and (> entry-at 1) (eq (get-text-property (1- entry-at) 
'logview-entry) entry))
+          (setq entry-at (or (previous-single-property-change entry-at 
'logview-entry) 1)))
+      (when (setq entry-at (or (next-single-property-change entry-at 
'logview-entry)
+                               (when (and (> entry-at 1) (get-text-property 
(1- entry-at) 'logview-entry))
+                                 (previous-single-property-change entry-at 
'logview-entry))))
+        (setq entry (get-text-property entry-at 'logview-entry))))
+    (if entry
+        (cons entry entry-at)
+      (unless position
+        (user-error "Unable to locate any log entries")))))
+
+(defun logview--iterate-entries-forward (position callback &optional 
only-visible validator skip-current)
   "Invoke CALLBACK for successive valid log entries forward.
-Iteration starts at the current entry and continues forward until
-CALLBACK returns nil or end of buffer is reached.
 
-CALLBACK is called with three arguments: beginning of the entry,
-end of its first line and its end (the last two are equal unless
-the entry spans multiple lines).  CALLBACK may not access match
-data and must make sure point and match data are preserved.
+Iteration starts at the entry around POSITION (or the next, if
+SKIP-CURRENT is non-nil) and continues forward until CALLBACK
+returns nil or end of buffer is reached.  This function does not
+alter the point, nor is it affected in any way by CALLBACK or
+VALIDATOR altering it.
+
+CALLBACK is called with two arguments: value of the
+`logview-entry' property and the beginning of the entry.
 
 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.  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)
-          (entry-end)
-          (limit       (point-max))
-          (invalid))
-      (while (progn
-               (forward-line)
-               (setq invalid (or (and only-visible (invisible-p entry-begin))
-                                 (and validator    (not (funcall validator)))))
-               (setq after-first-line (point)
-                     entry-end        (if (re-search-forward 
logview--entry-regexp nil t)
-                                          (match-beginning 0)
-                                        limit))
-               (when (or invalid (funcall callback entry-begin 
after-first-line entry-end))
-                 (/= (setq entry-begin entry-end) limit)))))))
-
-(defun logview--iterate-entries-backward (callback &optional only-visible 
validator)
+are skipped too.  VALIDATOR is called with the same parameters as
+CALLBACK."
+  (let ((entry+start (logview--do-locate-current-entry position)))
+    (when entry+start
+      (let ((entry    (car entry+start))
+            (entry-at (cdr entry+start))
+            (limit    (1+ (buffer-size))))
+        (unless (and skip-current (>= (setq entry-at (logview--entry-end entry 
entry-at)) limit))
+          (while (progn (setq entry (or (get-text-property entry-at 
'logview-entry)
+                                        (progn (logview--find-region-entries 
entry-at (+ entry-at logview--lazy-region-size))
+                                               (get-text-property entry-at 
'logview-entry))))
+                        (and (or (and only-visible (invisible-p entry-at))
+                                 (and validator (not (funcall validator entry 
entry-at)))
+                                 (funcall callback entry entry-at))
+                             (< (setq entry-at (logview--entry-end entry 
entry-at)) limit)))))))))
+
+(defun logview--iterate-entries-backward (position callback &optional 
only-visible validator skip-current)
   "Invoke CALLBACK for successive valid log entries backward.
-Iteration starts at the previous entry (not the current!) and
-continues backward until CALLBACK returns nil or beginning of
-buffer is reached.
-
 See `logview--iterate-entries-forward' for details."
-  (when (logview--match-current-entry)
-    (let ((entry-begin (match-beginning 0))
-          (entry-end))
-      (while (and (= (forward-line -1) 0)
-                  (or (looking-at logview--entry-regexp)
-                      (re-search-backward logview--entry-regexp nil t))
-                  (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 (point) entry-end)
-                          (goto-char entry-begin)))))))))
-
-(defun logview--iterate-successive-entries (n callback &optional only-visible 
validator)
+  (let ((entry+start (logview--do-locate-current-entry position)))
+    (when entry+start
+      (let ((entry    (car entry+start))
+            (entry-at (cdr entry+start)))
+        (unless (and skip-current (<= (setq entry-at (1- entry-at)) 0 ))
+          (while (and (setq entry (or (get-text-property entry-at 
'logview-entry)
+                                      (progn (logview--find-region-entries 
(max 1 (- entry-at logview--lazy-region-size)) (1+ entry-at) t)
+                                             (get-text-property entry-at 
'logview-entry))))
+                      (progn (unless (or (= entry-at 1) (not (eq 
(get-text-property (1- entry-at) 'logview-entry) entry)))
+                               (setq entry-at (or 
(previous-single-property-change entry-at 'logview-entry) 1)))
+                             (when (or (and only-visible (invisible-p 
entry-at))
+                                       (and validator (not (funcall validator 
entry entry-at)))
+                                       (funcall callback entry entry-at))
+                               (> (setq entry-at (1- entry-at)) 0))))))))))
+
+(defun logview--iterate-successive-entries (position n callback &optional 
only-visible validator)
   (when (/= n 0)
     (let ((direction (cl-signum n)))
-      (funcall (if (> n 0) 'logview--iterate-entries-forward 
'logview--iterate-entries-backward)
-               (lambda (begin after-first-line entry-end)
-                 (funcall callback begin after-first-line entry-end)
+      (funcall (if (> n 0) #'logview--iterate-entries-forward 
#'logview--iterate-entries-backward)
+               position
+               (lambda (entry entry-at)
+                 (funcall callback entry entry-at)
                  (/= (setq n (- n direction)) 0))
                only-visible validator)))
   n)
 
 (defun logview--iterate-entries-in-region (begin end callback &optional 
only-visible validator)
-  (goto-char (min begin end))
   (let ((limit (max begin end)))
-    (logview--iterate-entries-forward (lambda (begin after-first-line end)
-                                        (funcall callback begin 
after-first-line end)
-                                        (< end limit))
+    (logview--iterate-entries-forward (min begin end)
+                                      (lambda (entry entry-at)
+                                        (funcall callback entry entry-at)
+                                        (< (logview--entry-end entry entry-at) 
limit))
                                       only-visible validator)))
 
 
+(defun logview--refilter ()
+  (logview--retire-hiding-symbol 'logview--filtered-symbol)
+  (logview--update-invisibility-spec)
+  (logview--std-temporarily-widening
+    (font-lock-flush)))
+
+(defun logview--maybe-refontify-region (region)
+  (when region
+    (font-lock-flush (car region) (cdr region))))
+
+
 (defun logview--maybe-pulse-current-entry (&optional why)
   (when (or (null why) (memq why logview-pulse-entries))
-    (save-match-data
-      (save-excursion
-        (when (logview--match-current-entry)
-          (pulse-momentary-highlight-region (match-beginning 0)
-                                            (if (equal 
(logview--match-successive-entries 1) 0)
-                                                (match-beginning 0)
-                                              (point-max))
-                                            'logview-pulse))))))
+    (save-excursion
+      (logview--locate-current-entry entry start
+        (pulse-momentary-highlight-region start (logview--entry-end entry 
start) 'logview-pulse)))))
 
 
 (defun logview--update-mode-name ()
@@ -2294,47 +2287,44 @@ See `logview--iterate-entries-forward' for details."
                       (format "Logview/%s" logview--submode-name)))))
 
 (defun logview--update-invisibility-spec ()
-  (let ((invisibility-spec '(logview-hidden-entry logview-hidden-details)))
-    (if logview--submode-level-alist
-        (let ((show        (null logview--min-shown-level))
-              (always-show nil))
-          (dolist (level-pair logview--submode-level-data)
-            (when (equal (car level-pair) logview--min-shown-level)
-              (setq show t))
-            (when (equal (car level-pair) logview--min-always-shown-level)
-              (setq always-show t))
-            (unless show
-              (push (aref (cdr level-pair) 0) invisibility-spec))
-            (unless always-show
-              (push (aref (cdr level-pair) 1) invisibility-spec))))
-      (push 'logview-filtered invisibility-spec))
+  (let ((invisibility-spec (list logview--hidden-details-symbol 
logview--hidden-entry-symbol logview--filtered-symbol)))
     (when logview--hide-all-details
       (push 'logview-details invisibility-spec))
-    (setq buffer-invisibility-spec
-          (if logview-show-ellipses
-              (mapcar (lambda (x) (cons x t)) invisibility-spec)
-            invisibility-spec))
-    ;; This weird looking command was suggested in
+    (when logview-show-ellipses
+      (setq invisibility-spec (mapcar (lambda (x) (cons x t)) 
invisibility-spec)))
+    ;; Try to work nicely with other packages, e.g. minor modes.
+    (when (consp buffer-invisibility-spec)
+      (let ((case-fold-search nil))
+        (dolist (element buffer-invisibility-spec)
+          (unless (string-match "^logview-" (symbol-name (cond ((symbolp 
element)            element)
+                                                               ((symbolp 
(car-safe element)) (car-safe element)))))
+            (push element invisibility-spec)))))
+    (setq buffer-invisibility-spec (nreverse invisibility-spec))
+    ;; This weird-looking command was suggested in
     ;; irc.freenode.net#emacs and seems to force buffer redraw.
     ;; Otherwise change to 'buffer-invisibility-spec' doesn't have
     ;; immediate effect here.
     (force-mode-line-update)))
 
+(defun logview--retire-hiding-symbol (symbol-var)
+  (set symbol-var (intern (replace-regexp-in-string "[0-9]+$" (lambda 
(generation) (number-to-string (1+ (string-to-number generation))))
+                                                    (symbol-name (symbol-value 
symbol-var)) t t))))
 
+
+;; Return non-nil if filters have changed.
 (defun logview--parse-filters (&optional to-reset)
-  (logview--do-parse-filters logview--current-filter-text to-reset
-                             (lambda (min-shown-level min-always-shown-level 
canonical-filter-text name-filter thread-filter message-filter)
-                               (logview--set-min-level min-shown-level 
min-always-shown-level)
-                               (setq logview--current-filter-text 
canonical-filter-text
-                                     logview--name-filter         name-filter
-                                     logview--thread-filter       thread-filter
-                                     logview--message-filter      
message-filter)
-                               (logview--update-mode-name))))
-
-(defun logview--do-parse-filters (filters to-reset callback)
-  (let (min-shown-level
+  (let ((filters (logview--do-parse-filters logview--current-filter-text 
to-reset)))
+    (unless (prog1 (equal (cdar logview--current-filter) (cdar filters))
+              (setq logview--current-filter      filters
+                    logview--current-filter-text (or (caar filters) "")))
+      (logview--refilter)
+      (logview--update-mode-name)
+      t)))
+
+(defun logview--do-parse-filters (filters &optional to-reset)
+  (let (non-discarded-lines
+        min-shown-level
         min-always-shown-level
-        non-discarded-lines
         include-name-regexps
         exclude-name-regexps
         include-thread-regexps
@@ -2367,10 +2357,62 @@ See `logview--iterate-entries-forward' for details."
                           ("m+" (push regexp include-message-regexps))
                           ("m-" (push regexp exclude-message-regexps))))))))
            t))))
-    (funcall callback  min-shown-level min-always-shown-level (apply 'concat 
(nreverse non-discarded-lines))
-             (logview--build-filter include-name-regexps    
exclude-name-regexps)
-             (logview--build-filter include-thread-regexps  
exclude-thread-regexps)
-             (logview--build-filter include-message-regexps 
exclude-message-regexps))))
+    (setq min-shown-level         (unless (equal min-shown-level (caar 
logview--submode-level-data))
+                                    (cadr (assoc min-shown-level 
logview--submode-level-data)))
+          min-always-shown-level  (cadr (assoc min-always-shown-level 
logview--submode-level-data))
+          include-name-regexps    (logview--standardize-regexp-options 
include-name-regexps)
+          exclude-name-regexps    (logview--standardize-regexp-options 
exclude-name-regexps)
+          include-thread-regexps  (logview--standardize-regexp-options 
include-thread-regexps)
+          exclude-thread-regexps  (logview--standardize-regexp-options 
exclude-thread-regexps)
+          include-message-regexps (logview--standardize-regexp-options 
include-message-regexps)
+          exclude-message-regexps (logview--standardize-regexp-options 
exclude-message-regexps))
+    ;; Deliberately not checking `min-always-shown-level': it has no effect 
without other
+    ;; filters.
+    (when (or min-shown-level include-name-regexps exclude-name-regexps
+              include-thread-regexps exclude-thread-regexps 
include-message-regexps exclude-message-regexps)
+      (cons (list (apply 'concat (nreverse non-discarded-lines)) 
min-shown-level min-always-shown-level include-name-regexps exclude-name-regexps
+                  include-thread-regexps exclude-thread-regexps 
include-message-regexps exclude-message-regexps)
+            (let ((level-form (if (and min-shown-level min-always-shown-level) 
'level '(logview--entry-level entry)))
+                  clauses)
+              (when min-shown-level
+                (push `(<= ,level-form ,min-shown-level) clauses))
+              (push (logview--build-validator-regexp-clause 
include-name-regexps    exclude-name-regexps    logview--name-group)    clauses)
+              (push (logview--build-validator-regexp-clause 
include-thread-regexps  exclude-thread-regexps  logview--thread-group)  clauses)
+              (push (logview--build-validator-regexp-clause 
include-message-regexps exclude-message-regexps logview--message-group) clauses)
+              (setq clauses (delq nil clauses))
+              (let ((validator (if (cdr clauses) `(and ,@(nreverse clauses)) 
(car clauses))))
+                (when min-always-shown-level
+                  (setq validator `(or (<= ,level-form 
,min-always-shown-level) ,validator)))
+                (when (eq level-form 'level)
+                  (setq validator `(let ((level (logview--entry-level entry))) 
,validator)))
+                ;; Here `eval' is used to translate the lambda into a closure.
+                (byte-compile (eval `(lambda (entry start) (ignore start) 
,validator) t))))))))
+
+;; To prevent refiltering on insignificant changes, we enforce canonical 
option ordering
+;; and drop any duplicates.
+(defun logview--standardize-regexp-options (options)
+  (delete-consecutive-dups (sort options #'string<)))
+
+(defun logview--build-validator-regexp-clause (include-regexps exclude-regexps 
entry-group)
+  (when (or include-regexps exclude-regexps)
+    (let* ((string-fetcher (if (= entry-group logview--message-group)
+                               `(logview--entry-message entry start)
+                             `(logview--entry-group entry start ,entry-group)))
+           (string-form    (if (and include-regexps exclude-regexps) 'string 
string-fetcher))
+           subclauses)
+      (when include-regexps
+        (push `(string-match ,(logview--build-filter-regexp include-regexps) 
,string-form) subclauses))
+      (when exclude-regexps
+        (push `(not (string-match ,(logview--build-filter-regexp 
exclude-regexps) ,string-form)) subclauses))
+      (let ((clause (if (cdr subclauses) `(and ,@(nreverse subclauses)) (car 
subclauses))))
+        (when (eq string-form 'string)
+          (setq clause `(let ((string ,string-fetcher)) ,clause)))
+        clause))))
+
+;; 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)
+  (mapconcat #'identity options "\\|"))
 
 (defun logview--iterate-filter-text-lines (filters callback)
   (with-temp-buffer
@@ -2421,174 +2463,44 @@ next line, which is usually one line beyond END."
                                             (push 
(buffer-substring-no-properties line-begin (1+ end)) filter-lines))))
     (apply #'concat (sort filter-lines #'string<))))
 
-(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 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 set-up-entries cancel-explicit-hiding (not (equal 
logview--applied-filter-id filter-id)))
-      (logview--std-matching-and-altering
-        (save-restriction
-          (widen)
-          (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              (unless have-level 
(logview--hide-entry-callback 'logview-filtered)))
-                     (shower             (unless have-level 
(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)
-                     (put-text-property after-first-line end 'face (aref 
level-data 2))
-                     (put-text-property (logview--linefeed-back 
after-first-line) (logview--linefeed-back end)
-                                        'invisible (list (aref level-data 0) 
'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 (or shower (aref level-data 5)) begin 
after-first-line end)
-                         (setq num-visible (1+ num-visible)))
-                     (funcall (or hider (aref level-data 4)) 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 have-level
-                       (setq level-data (cdr (assoc (match-string 
logview--level-group) logview--submode-level-data))))
-                     (when set-up-entries
-                       (when have-level
-                         ;; Point is guaranteed to be at the start of the next 
line.
-                         (put-text-property (match-beginning 0) (point) 'face 
(aref level-data 2))
-                         (put-text-property (logview--linefeed-back-checked 
(match-beginning 0)) (logview--linefeed-back (point))
-                                            'invisible (list (aref level-data 
0)))
-                         (add-face-text-property (match-beginning 
logview--level-group)
-                                                 (match-end       
logview--level-group)
-                                                 (aref level-data 3)))
-                       (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
-    ;; 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)
-  (lambda (begin after-first-line end)
-    (let ((value (get-text-property begin 'invisible)))
-      (when (memq hider value)
-        (put-text-property (logview--linefeed-back-checked begin) 
(logview--linefeed-back after-first-line)
-                           'invisible (remq hider value))
-        (when (> end after-first-line)
-          (put-text-property (logview--linefeed-back after-first-line) 
(logview--linefeed-back end)
-                             'invisible (remq hider (get-text-property 
after-first-line 'invisible))))))))
-
-(defun logview--hide-entry-callback (hider)
-  (lambda (begin after-first-line end)
-    (let ((value (get-text-property begin 'invisible)))
-      (unless (memq hider value)
-        (put-text-property (logview--linefeed-back-checked begin) 
(logview--linefeed-back after-first-line)
-                           'invisible (cons hider value))
-        (when (> end after-first-line)
-          (put-text-property (logview--linefeed-back after-first-line) 
(logview--linefeed-back end)
-                             'invisible (cons hider (get-text-property 
after-first-line 'invisible))))))))
+(defun logview--find-region-entries (region-start region-end &optional 
dont-stop-early)
+  (logview--std-altering
+    (save-excursion
+      (save-match-data
+        (let ((case-fold-search         nil)
+              (buffer-invisibility-spec nil))
+          (goto-char region-start)
+          (forward-line 0)
+          (when (or (looking-at logview--entry-regexp)
+                    (re-search-backward logview--entry-regexp nil t)
+                    (re-search-forward  logview--entry-regexp nil t))
+            (setq region-end (min region-end (1+ (buffer-size))))
+            (let* ((match-data  (match-data t))
+                   (entry-start (car match-data))
+                   (have-level  (memq 'level logview--submode-features)))
+              (while (and (or dont-stop-early (null (get-text-property 
entry-start 'logview-entry)))
+                          (let* ((details-start   (progn (forward-line 1) 
(point)))
+                                 (have-next-entry (re-search-forward 
logview--entry-regexp nil t))
+                                 (entry-end       (if have-next-entry 
(match-beginning 0) (point-max)))
+                                 ;; See description of `logview-entry' above.
+                                 (logview-entry   (make-vector 12 nil)))
+                            (aset logview-entry 0 (- entry-end entry-start))
+                            (let ((points (cdr match-data)))
+                              (dotimes (k 9)
+                                (let ((point (pop points)))
+                                  (aset logview-entry (1+ k) (when point (- 
point entry-start))))))
+                            (when (< details-start entry-end)
+                              (aset logview-entry 10 (- details-start 
entry-start)))
+                            (when have-level
+                              (aset logview-entry 11 (cadr (assoc 
(logview--entry-group logview-entry entry-start logview--level-group)
+                                                                  
logview--submode-level-data))))
+                            (match-data t match-data)
+                            (put-text-property entry-start entry-end 
'logview-entry logview-entry)
+                            (setq entry-start entry-end)
+                            (< entry-start region-end)))))))))))
 
 
 (defun logview--iterate-split-alists (callback &rest alists)
@@ -2707,16 +2619,96 @@ This list is preserved across Emacs session in
 
 ;;; Internal commands meant as hooks.
 
-(defun logview--split-region-into-entries (begin end &optional old-length)
-  "Parse log entries in given region.
-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-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--fontify-region (region-start region-end _loudly)
+  (logview--std-temporarily-widening
+    ;; We are very fast.  Don't fontify too little to avoid overhead.
+    (when (and (< region-end (point-max)) (not (get-text-property (1+ 
region-end) 'fontified)))
+      (let ((expanded-region-end (+ region-start logview--lazy-region-size)))
+        (when (< region-end expanded-region-end)
+          (setq region-end (or (next-single-property-change (1+ region-end) 
'fontified nil expanded-region-end) expanded-region-end)))))
+    (when (and (> region-start (point-min)) (not (get-text-property (1- 
region-start) 'fontified)))
+      (let ((expanded-region-start (max 1 (- region-end 
logview--lazy-region-size))))
+        (when (> region-start expanded-region-start)
+          (setq region-start (or (previous-single-property-change (1- 
region-start) 'fontified nil expanded-region-start) expanded-region-start)))))
+    (let ((first-entry-start (cdr (logview--do-locate-current-entry 
region-start))))
+      (when first-entry-start
+        (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))
+                  found-anything-visible)
+              (logview--iterate-entries-forward
+               region-start
+               (lambda (entry start)
+                 (let ((end (logview--entry-end entry start))
+                       filtered)
+                   (if (or (null validator) (funcall validator entry start))
+                       (progn
+                         (when have-level
+                           (let ((entry-faces (aref 
logview--submode-level-faces (logview--entry-level entry))))
+                             (put-text-property start end 'face (car 
entry-faces))
+                             (add-face-text-property 
(logview--entry-group-start entry start logview--level-group)
+                                                     (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))
+                         (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)
+                                                   'logview-name))
+                         (when have-thread
+                           (add-face-text-property (logview--entry-group-start 
entry start logview--thread-group)
+                                                   (logview--entry-group-end   
entry start logview--thread-group)
+                                                   'logview-thread)))
+                     (setq filtered t))
+                   (when (logview--update-entry-invisibility start 
(logview--entry-details-start entry start) end filtered 'propagate 'propagate)
+                     (setq found-anything-visible t))
+                   (or (< end region-end)
+                       ;; There appears to be a bug in displaying code for the 
unlikely case
+                       ;; that fontifying function hides all the text in the 
region it has
+                       ;; been called for: Emacs still displays an empty line 
or at least the
+                       ;; ellipses to denote hidden text (i.e. not merged with 
the previous
+                       ;; ellipses).  So, to avoid this bug we just continue.  
Besides, font
+                       ;; lock would do this anyway.
+                       (not found-anything-visible)))))))))))
+  `(jit-lock-bounds ,region-start . ,region-end))
+
+;; Returns non-nil if any part of the entry is visible as a result.
+(defun logview--update-entry-invisibility (start details-start end filtered 
entry-manually-hidden details-manually-hidden)
+  (let ((first-line-end-lf-back (logview--linefeed-back (or details-start 
end)))
+        (invisible              (get-text-property (or details-start start) 
'invisible))
+        new-invisible
+        fully-invisible)
+    (dolist (element (if (listp invisible) invisible (list invisible)))
+      (cond ((and (eq filtered                'propagate) (eq element 
logview--filtered-symbol))
+             (setq filtered t))
+            ((and (eq entry-manually-hidden   'propagate) (eq element 
logview--hidden-entry-symbol))
+             (setq entry-manually-hidden t))
+            ((and (eq details-manually-hidden 'propagate) (eq element 
logview--hidden-details-symbol))
+             (setq details-manually-hidden t))
+            ((not (and (symbolp element) (string-match "^logview-" 
(symbol-name element))))
+             (push element new-invisible))))
+    (when (eq entry-manually-hidden t)
+      (push logview--hidden-entry-symbol new-invisible)
+      (setq fully-invisible t))
+    (when (eq filtered t)
+      (push logview--filtered-symbol new-invisible)
+      (setq fully-invisible t))
+    (if new-invisible
+        (put-text-property (logview--linefeed-back-checked start) 
first-line-end-lf-back 'invisible (setq new-invisible (nreverse new-invisible)))
+      (remove-list-of-text-properties (logview--linefeed-back-checked start) 
first-line-end-lf-back '(invisible)))
+    (when details-start
+      (when (eq details-manually-hidden t)
+        (push logview--hidden-details-symbol new-invisible))
+      (push 'logview-details new-invisible)
+      (put-text-property first-line-end-lf-back (logview--linefeed-back end) 
'invisible new-invisible))
+    (not fully-invisible)))
 
 (defun logview--buffer-substring-filter (begin end delete)
   "Optionally remove invisible text from the substring."
@@ -2736,16 +2728,14 @@ Optional third argument is to make the function 
suitable for
 (defun logview--isearch-filter-predicate (begin end)
   (and (funcall (default-value 'isearch-filter-predicate) begin end)
        (or (not logview-search-only-in-messages)
-             (logview--std-matching
-               (save-match-data
-                 (save-restriction
-                   (widen)
-                   (goto-char begin)
-                   (or (not (logview--match-current-entry))
-                       (and (or (>= (match-beginning 0) end)
-                                (and (<= (match-end 0) begin)
-                                     (or (not 
(logview--match-successive-entries 1 t))
-                                         (>= (match-beginning 0) 
end))))))))))))
+           (logview--std-temporarily-widening
+             (let ((entry+start (logview--do-locate-current-entry begin)))
+               (when entry+start
+                 (let* ((entry         (car entry+start))
+                        (start         (cdr entry+start))
+                        (message-start (logview--entry-message-start entry 
start))
+                        (message-end   (logview--entry-end entry start)))
+                   (and (>= begin message-start) (<= end message-end)))))))))
 
 ;; Exists for potential future expansion.
 (defun logview--kill-emacs-hook ()
@@ -2805,8 +2795,7 @@ Optional third argument is to make the function suitable 
for
           (when (string-prefix-p logview-filter-edit--filters-hint-comment 
filters)
             (setq filters (substring filters (length 
logview-filter-edit--filters-hint-comment))))
           (setq logview--current-filter-text filters)
-          (logview--parse-filters)
-          (logview--apply-parsed-filters))))))
+          (logview--parse-filters))))))
 
 (defun logview-filter-edit--initialize-text (&optional filters-text)
   (delete-region 1 (1+ (buffer-size)))
@@ -2867,7 +2856,7 @@ Optional third argument is to make the function suitable 
for
                     (put-text-property line-begin begin 'face 
'logview-edit-filters-type-prefix)
                     (let ((level-string (buffer-substring-no-properties begin 
end))
                           (known-levels (with-current-buffer 
logview-filter-edit--parent-buffer
-                                          logview--submode-level-alist)))
+                                          logview--submode-level-data)))
                       (while (and level-string known-levels)
                         (if (string= (caar known-levels) level-string)
                             (setq level-string nil)


Reply via email to