branch: elpa/logview commit 03b085a4bd83e41c2f48b44fb8c54468b6172196 Author: Paul Pogonyshev <pogonys...@gmail.com> Commit: Paul Pogonyshev <pogonys...@gmail.com>
Add views: defined sets of filters, between which you can switch quickly. --- README.md | 52 +++++++- TODO.md | 10 +- logview.el | 423 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 415 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 9120796bac..e468491a4d 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,17 @@ See [more detailed description below](#filters-explained). * Reset all filters: `R` * Reset all filters, widen and show all explicitly hidden entries: `r e` +#### Views + +See [more detailed description below](#views-explained). + +* Switch to a view: `v` +* Save the current filters as a view for this submode: `V s` +* Save the current filters as a global view: `V S` +* Edit submode views: `V e` (pops up a separate buffer) +* Edit all views: `V E` (pops up a separate buffer) +* Delete a view by name: `V d` + #### Explicitly hide or show individual entries * Hide one entry: `h` @@ -218,7 +229,7 @@ variable `case-fold-search`. Filters are matched against relevant entry parts as strings, not against the whole buffer. Therefore, you can use `^` and `$` special characters for the expected meaning. For example, adding `^org` as -name exclusion filter will hide all entries where logger name _begins_ +name exclusion filter will hide all entries where logger name begins with string ‘org’. Unlike name and thread filters, message filters can span multiple @@ -230,3 +241,42 @@ Commands `a`, `A`, `t` and `T` default to the name (or thread) of the current entry. You can also use `C-p` (`<up>`) to browse history of previously entered values and `C-n` (`<down>`) for a few default values. + + +### Views<a id="views-explained"></a> + +Views are named sets of filters that you can activate quickly. They +are especially useful if you use name or message filters a lot, and +often find yourself typing in the same filters over and over again. + +The easiest way to define a view is by first adding all the filters +you need. This way you can see in the buffer if the filtering result +matches what you expect. After you are satisfied, type `V s` and a +name for the new view. Notice that the mode line now displays name of +the view in square brackets after the submode name, e.g.: + + Logview/SLF4J [useful-view-1] + +Now type `R` to reset all the filters. All previously hidden entries +will be shown again and the view name disappear from the mode line. +However, to restore the filters now you don’t have to re-create them +one-by-one. Simply type `v` and whatever name you used when saving +your first view. You can also use text completion to pick among all +the defined views. + +Remember that further filtering doesn’t affect view definition. If +you want to change a view, save filters as a view with the same name +again, and confirm that you do want to replace the previous +definition. Alternative way is to edit views using `V e`. This pops +up a separate buffer just like `f` command does, but instead of +filters you will edit all defined views for the current submode at +once. This way you can change existing definitions, delete unneeded +or add more. Commands like `V s` or `V d` (delete a view by name) can +be seen as just a convenience. + +Views come in two kinds: globally accessible and bound to a specific +submode. This distinction is important if you use logs of different +kinds. Most often you need submode-specific views, because text +filters usually can’t be meaningfully applied without changes to +different programs. When you use `v` command, only the views for the +current submode plus any global views are available for selection. diff --git a/TODO.md b/TODO.md index 48b8f447b3..5459ebcecb 100644 --- a/TODO.md +++ b/TODO.md @@ -3,15 +3,13 @@ * Much improved submode guessing. This is partially done in 0.5 by using 'datetime' library. -* Views: named sets of filters that you can save and reuse later. - ## Other ideas * More movement commands: move inside the same thread, move across - entries of some view (see above) without activating it. This is not - so difficult to implement, but requires pondering on how to make - the commands comfortable to use. + entries of some view without activating it. This is not so + difficult to implement, but requires pondering on how to make the + commands comfortable to use. * Add a way to always show errors/warnings even if they would otherwise be filtered out. Explicit hiding should still take @@ -28,7 +26,7 @@ * Sections: somehow make certain entries stand out and add navigation to the section start, narrow to section etc. The idea is that sections can be made to span single request to your server - (optionally bind to threads too). Probably requires views. + (optionally bind to threads too). * Replace timestamps with difference (likely to section start, as defined above) on demand. E.g. something like this: diff --git a/logview.el b/logview.el index 1ebdb0971d..279da9f86e 100644 --- a/logview.el +++ b/logview.el @@ -337,6 +337,11 @@ To temporarily change this on per-buffer basis type \\<logview-mode-map>\\[logvi :group 'logview :type 'boolean) +(defcustom logview-views-file (locate-user-emacs-file "logview.views") + "Simple text file in which defined views are stored." + :group 'logview + :type 'file) + (defgroup logview-faces nil "Faces for Logview mode." @@ -459,6 +464,7 @@ To temporarily change this on per-buffer basis type \\<logview-mode-map>\\[logvi (defconst logview--valid-filter-prefixes (append '("lv") logview--valid-text-filter-prefixes)) +(defvar-local logview--submode-name nil) (defvar-local logview--entry-regexp nil) (defvar-local logview--submode-features nil) @@ -493,16 +499,32 @@ Levels are ordered least to most important.") (defvar-local logview--process-buffer-changes nil) +(defvar logview--views nil) +(defvar logview--views-initialized nil) +(defvar logview--views-need-saving nil) + +(defvar logview--view-name-history) + (defvar-local logview--filter-editing-buffer nil) +(defvar logview--view-editing-buffer nil) -(defvar-local logview-filter-edit--parent-buffer nil) -(defvar-local logview-filter-edit--window-configuration nil) +(defvar-local logview-filter-edit--parent-buffer nil) +(defvar-local logview-filter-edit--window-configuration nil) +(defvar-local logview-filter-edit--editing-views nil) +(defvar-local logview-filter-edit--editing-views-for-submode nil) -(defvar logview-filter-edit--hint-comment +(defvar logview-filter-edit--filters-hint-comment "# Press C-c C-c to save edited filters, C-c C-k to quit without saving. ") +(defvar logview-filter-edit--views-hint-comment + "# Press C-c C-c to save edited views, C-c C-k to quit without saving. +") + +(defconst logview--view-header-regexp (rx bol (group "view") " " (group (1+ nonl)) eol)) +(defconst logview--view-submode-regexp (rx bol (group "submode") " " (group (1+ nonl)) eol)) + (defvar logview--cheat-sheet '(("Movement" @@ -521,7 +543,7 @@ Levels are ordered least to most important.") (logview-show-errors-warnings-and-information "Show errors, warnings and information") (logview-show-errors-warnings-information-and-debug "Show all levels except trace") (logview-show-all-levels "Show entries of all levels") - (logview-show-only-as-important "Show entries ‘as important’ as current one")) + (logview-show-only-as-important "Show entries ‘as important’ as the current one")) ("Text-based filtering" (logview-edit-filters "Edit filters as text in a separate buffer") (logview-add-include-name-filter logview-add-exclude-name-filter "Add name include / exclude filter") @@ -534,13 +556,20 @@ Levels are ordered least to most important.") (logview-reset-message-filters "Reset message filters") (logview-reset-all-filters "Reset all filters") (logview-reset-all-filters-restrictions-and-hidings "Reset filters, widen and show explicitly hidden entries")) + ("Views" + (logview-switch-to-view "Switch to a view") + (logview-save-filters-as-view-for-submode "Save the filters as a view for the current submode") + (logview-save-filters-as-global-view "Save the filters as a globally available view") + (logview-edit-submode-views "Edit views for the current submode") + (logview-edit-all-views "Edit all views") + (logview-delete-view "Delete a view")) ("Explicitly hide or show entries" (logview-hide-entry "Hide entry") (logview-hide-region-entries "Hide entries in the region") (logview-show-entries "Show some explicitly hidden entries") (logview-show-region-entries "Show explicitly hidden entries in the region")) ("Hide or show details of individual entries" - (logview-toggle-entry-details "Toggle details of current entry") + (logview-toggle-entry-details "Toggle details of the current entry") (logview-toggle-region-entry-details "Toggle details of entries in the region") (logview-toggle-details-globally "Toggle details in the whole buffer") "Here ‘details’ are the message lines after the first.") @@ -652,6 +681,13 @@ that the line is not the first in the buffer." ("r m" logview-reset-message-filters) ("R" logview-reset-all-filters) ("r e" logview-reset-all-filters-restrictions-and-hidings) + ;; View commands. + ("v" logview-switch-to-view) + ("V s" logview-save-filters-as-view-for-submode) + ("V S" logview-save-filters-as-global-view) + ("V e" logview-edit-submode-views) + ("V E" logview-edit-all-views) + ("V d" logview-delete-view) ;; Explicit entry hiding/showing commands. ("h" logview-hide-entry) ("H" logview-hide-region-entries) @@ -980,6 +1016,7 @@ hidden." ;;; Filtering by name/thread commands. (defun logview-edit-filters () + "Edit the current filters in a separate buffer." (interactive) (let ((self (current-buffer)) (windows (current-window-configuration)) @@ -992,7 +1029,8 @@ hidden." (unless (eq major-mode 'logview-filter-edit-mode) (logview-filter-edit-mode)) (setq logview-filter-edit--parent-buffer self - logview-filter-edit--window-configuration windows) + logview-filter-edit--window-configuration windows + logview-filter-edit--editing-views nil) (logview-filter-edit--initialize-text filters))) (defun logview-add-include-name-filter () @@ -1131,6 +1169,121 @@ entries and cancel any narrowing restrictions." (logview--apply-parsed-filters also-cancel-explicit-hiding))) + +;;; View commands. + +(defun logview-switch-to-view (view) + "Switch to a previously defined view. +Interactively, read the view name from the minibuffer." + (interactive (logview--choose-view "Switch to view: ")) + (setq logview--current-filter-text (plist-get view :filters)) + (logview--parse-filters) + (logview--apply-parsed-filters)) + +(defun logview-save-filters-as-view-for-submode (name) + "Save the current filter set as a view for the current submode. +Interactively, read the name for the new view from the +minibuffer." + (interactive (list nil)) + (logview--do-save-filters-as-view name nil)) + +(defun logview-save-filters-as-global-view (name) + "Save the current filter set as a global view. +Interactively, read the name for the new view from the +minibuffer." + (interactive (list nil)) + (logview--do-save-filters-as-view name t)) + +(defun logview-edit-submode-views () + "Edit views for the current submode in a separate buffer." + (interactive) + (logview--do-edit-views t)) + +(defun logview-edit-all-views () + "Edit all views in a separate buffer." + (interactive) + (logview--do-edit-views nil)) + +(defun logview-delete-view (view) + "Delete a view definition. +Interactively, read the view name from the minibuffer." + (interactive (logview--choose-view "Delete view: ")) + (setq logview--views (delq view (logview--views)) + logview--views-need-saving t) + (logview--update-mode-name)) + +(defun logview--choose-view (prompt) + (let (defined-names) + (dolist (view (logview--views)) + (when (or (null (plist-get view :submode)) (string= (plist-get view :submode) logview--submode-name)) + (push (plist-get view :name) defined-names))) + (unless defined-names + (user-error "There are no views defined for the current submode")) + (let ((name (logview--completing-read prompt defined-names nil t nil 'logview--view-name-history)) + (all-views (logview--views)) + view) + (while all-views + (let ((candidate (pop all-views))) + (when (and (string= (plist-get candidate :name) name) + (or (null (plist-get candidate :submode)) (string= (plist-get candidate :submode) logview--submode-name))) + (setq view candidate + all-views nil)))) + (list view)))) + +(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)) + (user-error "There are currently no filters")) + (unless name + (setq name (read-string "Name: " nil 'logview--view-name-history))) + (when (= (length name) 0) + (user-error "View name may not be empty")) + (let ((matches (lambda (view) + (and (string= (plist-get view :name) name) + (or global (null (plist-get view :submode)) (string= (plist-get view :submode) logview--submode-name)))))) + (dolist (view (logview--views)) + (when (funcall matches view) + (unless (y-or-n-p (format-message (if global + "There is already a view named `%s'. Replace it?" + "There is already a view named `%s' for this submode. Replace it?") + name)) + (user-error "View named `%s' already exists; try a different name" name)))) + (let (new-views) + (dolist (view (logview--views)) + (unless (funcall matches view) + (push view new-views))) + (push (list :name name :filters logview--current-filter-text) new-views) + (unless global + (plist-put (car new-views) :submode logview--submode-name)) + (setq logview--views (nreverse new-views) + logview--views-need-saving t) + (logview--update-mode-name) + (message (if global "Saved filters as a global view named `%s'" "Saved filters as a submode view named `%s'") name)))) + +(defun logview--do-edit-views (submode-only) + (let ((self (current-buffer)) + (windows (current-window-configuration)) + (submode (when submode-only logview--submode-name))) + (if (buffer-live-p logview--view-editing-buffer) + (when (and (buffer-modified-p logview--view-editing-buffer) + (not (eq (with-current-buffer logview--view-editing-buffer + logview-filter-edit--editing-views-for-submode) + submode))) + (pop-to-buffer logview--view-editing-buffer) + (unless (yes-or-no-p "Discard current view editing changes?") + (user-error "Another view editing is in progress"))) + (setq logview--view-editing-buffer (generate-new-buffer "Logview views"))) + (split-window-vertically) + (other-window 1) + (switch-to-buffer logview--view-editing-buffer) + (unless (eq major-mode 'logview-filter-edit-mode) + (logview-filter-edit-mode)) + (setq logview-filter-edit--parent-buffer self + logview-filter-edit--window-configuration windows + logview-filter-edit--editing-views t + logview-filter-edit--editing-views-for-submode submode) + (logview-filter-edit--initialize-text))) + + ;;; Explicit entry hiding/showing commands. @@ -1287,6 +1440,9 @@ well." (defun logview-toggle-details-globally (&optional arg) + "Toggle whether details are shown in the whole buffer. +If invoked with prefix argument, show details if the argument is +positive, hide otherwise." (interactive (list (or current-prefix-arg 'toggle))) (logview--toggle-option-locally 'logview--hide-all-details arg (called-interactively-p 'interactive) "All entry messages details are now hidden" @@ -1299,7 +1455,6 @@ well." (defun logview-toggle-copy-visible-text-only (&optional arg) "Toggle `logview-copy-visible-text-only' just for this buffer. - If invoked with prefix argument, enable the option if the argument is positive, disable it otherwise." (interactive (list (or current-prefix-arg 'toggle))) @@ -1309,7 +1464,6 @@ argument is positive, disable it otherwise." (defun logview-toggle-search-only-in-messages (&optional arg) "Toggle `logview-search-only-in-messages' just for this buffer. - If invoked with prefix argument, enable the option if the argument is positive, disable it otherwise." (interactive (list (or current-prefix-arg 'toggle))) @@ -1319,7 +1473,6 @@ argument is positive, disable it otherwise." (defun logview-toggle-show-ellipses (&optional arg) "Toggle `logview-show-ellipses' just for this buffer. - If invoked with prefix argument, enable the option if the argument is positive, disable it otherwise." (interactive (list (or current-prefix-arg 'toggle))) @@ -1638,11 +1791,12 @@ returns non-nil." (setcar timestamp-at (format "\\(?%d:%s\\)" logview--timestamp-group timestamp-regexp))) (let ((regexp (apply #'concat parts))) (when (string-match regexp test-line) - (setq logview--process-buffer-changes t + (setq logview--submode-name name + logview--process-buffer-changes t logview--entry-regexp regexp logview--submode-features features - logview--submode-level-alist nil - mode-name (format "Logview/%s" name)) + logview--submode-level-alist nil) + (logview--update-mode-name) (when (memq 'level features) (dolist (final-level logview--final-levels) (dolist (level (cdr (assoc final-level levels))) @@ -1841,6 +1995,21 @@ See `logview--iterate-entries-forward' for details." only-visible validator))) +(defun logview--update-mode-name () + (let ((view-name (catch 'found + (dolist (view (logview--views)) + (when (and (or (null (plist-get view :submode)) (string= (plist-get view :submode) logview--submode-name)) + (string= (plist-get view :filters) logview--current-filter-text)) + (throw 'found (plist-get view :name)))) + (let ((canonical-filter-text (logview--canonical-filter-text logview--current-filter-text))) + (dolist (view (logview--views)) + (when (and (or (null (plist-get view :submode)) (string= (plist-get view :submode) logview--submode-name)) + (string= (logview--canonical-filter-text (plist-get view :filters)) canonical-filter-text)) + (throw 'found (plist-get view :name)))))))) + (setq mode-name (if view-name + (format "Logview/%s [%s]" logview--submode-name view-name) + (format "Logview/%s" logview--submode-name))))) + (defun logview--update-invisibility-spec () (let ((invisibility-spec '(logview-filtered logview-hidden-entry logview-hidden-details)) (found nil)) @@ -1877,37 +2046,44 @@ See `logview--iterate-entries-forward' for details." include-message-regexps exclude-message-regexps) (when (> (length filters) 0) - (with-temp-buffer - (insert filters) - (goto-char 1) - (logview--iterate-filter-lines - (lambda (type line-begin begin end) - (let ((filter-line (not (member type '("#" "" nil)))) - (reset-this-filter (member type to-reset))) - (when reset-this-filter - (delete-region begin (point))) - (when (and (not (and filter-line reset-this-filter)) (or non-discarded-lines (not (string= type "")))) - (push (buffer-substring-no-properties line-begin (point)) non-discarded-lines)) - (when (and filter-line (not reset-this-filter)) - (if (string= type "lv") - (setq min-shown-level (buffer-substring-no-properties begin end)) - (let ((regexp (logview--filter-regexp begin end))) - (when (logview--valid-regexp-p regexp) - (pcase type - ("a+" (push regexp include-name-regexps)) - ("a-" (push regexp exclude-name-regexps)) - ("t+" (push regexp include-thread-regexps)) - ("t-" (push regexp exclude-thread-regexps)) - ("m+" (push regexp include-message-regexps)) - ("m-" (push regexp exclude-message-regexps))))))) - t))))) + (logview--iterate-filter-text-lines + filters + (lambda (type line-begin begin end) + (let ((filter-line (not (member type '("#" "" nil)))) + (reset-this-filter (member type to-reset))) + (when reset-this-filter + (delete-region begin (point))) + (when (and (not (and filter-line reset-this-filter)) (or non-discarded-lines (not (equal type "")))) + (push (buffer-substring-no-properties line-begin (point)) non-discarded-lines)) + (when (and filter-line (not reset-this-filter)) + (if (string= type "lv") + (setq min-shown-level (buffer-substring-no-properties begin end)) + (let ((regexp (logview--filter-regexp begin end))) + (when (logview--valid-regexp-p regexp) + (pcase type + ("a+" (push regexp include-name-regexps)) + ("a-" (push regexp exclude-name-regexps)) + ("t+" (push regexp include-thread-regexps)) + ("t-" (push regexp exclude-thread-regexps)) + ("m+" (push regexp include-message-regexps)) + ("m-" (push regexp exclude-message-regexps))))))) + t)))) (logview--set-min-level min-shown-level) (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)))) + logview--message-filter (logview--build-filter include-message-regexps exclude-message-regexps)) + (logview--update-mode-name))) + +(defun logview--iterate-filter-text-lines (filters callback) + (with-temp-buffer + (insert filters) + (unless (bolp) + (insert "\n")) + (goto-char 1) + (logview--iterate-filter-buffer-lines callback))) -(defun logview--iterate-filter-lines (callback) +(defun logview--iterate-filter-buffer-lines (callback) "Find successive filter specification in the current buffer. Buffer must be positioned at the start of a line. Iteration continues until CALLBACK returns nil or end of buffer is reached. @@ -1918,7 +2094,7 @@ or \"lv\" for valid filter types, \"#\" for comment line and \"\" for an empty line, or nil to indicate an erroneous line. BEGIN and END determine filter text boundaries (may span several lines for message filters. LINE-BEGIN is the beginnig of the line where -the entry starts; in case of filters this is a few charaters +the entry starts; in case of filters this is a few characters before BEGIN. Point is positioned at the start of next line, which is usually one line beyond END." (let ((case-fold-search nil) @@ -1940,6 +2116,14 @@ 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--canonical-filter-text (filters) + (let (filter-lines) + (logview--iterate-filter-text-lines filters + (lambda (type line-begin _begin end) + (unless (member type '(nil "" "#")) + (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))) @@ -2128,6 +2312,72 @@ which is usually one line beyond END." alists) (user-error "Unknown %s '%s'" type key))) +(defun logview--views () + "Return the list of all defined views. +Each element is a plist with properties :name, :filters and +:submode. More properties might be defined later. + +This list is preserved across Emacs session in +`logview-views-file'." + (unless logview--views-initialized + (with-temp-buffer + (insert-file-contents logview-views-file) + (setq logview--views (logview--parse-view-definitions) + logview--views-initialized t))) + logview--views) + +(defun logview--parse-view-definitions (&optional warn-about-garbage) + (catch 'done + (let (views + pending-name + pending-submode + filters-from) + (while t + (if (or (eobp) (looking-at logview--view-header-regexp)) + (progn (when pending-name + (save-excursion + (skip-syntax-backward "-" filters-from) + (push (list :name pending-name + :submode pending-submode + :filters (buffer-substring-no-properties filters-from (point))) + views))) + (when (eobp) + (throw 'done (nreverse views))) + (setq pending-name (match-string-no-properties 2)) + (forward-line) + (if (looking-at logview--view-submode-regexp) + (progn (setq pending-submode (match-string-no-properties 2)) + (forward-line)) + (setq pending-submode nil)) + (setq filters-from (point))) + (when (and warn-about-garbage (null pending-name) (not (looking-at (rx (0+ blank) (opt "#" (0+ nonl)) eol)))) + (if (yes-or-no-p "Non-comment text before the first view will be discarded; continue? ") + (setq warn-about-garbage nil) + (keyboard-quit))) + (forward-line)))))) + +(defun logview--insert-view-definitions (&optional predicate) + (dolist (view (logview--views)) + (when (or (null predicate) (funcall predicate view)) + (unless (bobp) + (insert "\n")) + (insert "view " (plist-get view :name) "\n") + (when (plist-get view :submode) + (insert "submode " (plist-get view :submode) "\n")) + (insert (plist-get view :filters)) + (unless (bolp) + (insert "\n"))))) + +(defun logview--save-views-if-needed () + (when logview--views-need-saving + (with-temp-buffer + (logview--insert-view-definitions) + (write-region (point-min) (point-max) logview-views-file nil 'silent) + (setq logview--views-need-saving nil)))) + +(defun logview--completing-read (&rest arguments) + (apply (if (fboundp 'ido-completing-read) 'ido-completing-read 'completing-read) arguments)) + ;;; Internal commands meant as hooks. @@ -2172,6 +2422,10 @@ Optional third argument is to make the function suitable for (or (not (logview--match-successive-entries 1 t)) (>= (match-beginning 0) end)))))))))))) +;; Exists for potential future expansion. +(defun logview--kill-emacs-hook () + (logview--save-views-if-needed)) + ;;; Logview Filter Edit mode. @@ -2197,31 +2451,55 @@ Optional third argument is to make the function suitable for (logview-filter-edit--quit nil)) (defun logview-filter-edit--quit (save) - (let ((parent logview-filter-edit--parent-buffer) - (windows logview-filter-edit--window-configuration) - (filters (when save - (buffer-substring-no-properties 1 (1+ (buffer-size)))))) - (kill-buffer) - (switch-to-buffer parent) - (set-window-configuration windows) - (when save - (setq logview--current-filter-text filters) - (logview--parse-filters) - (logview--apply-parsed-filters)))) - -(defun logview-filter-edit--initialize-text (text) - (unless (string-prefix-p logview-filter-edit--hint-comment text) - (setq text (concat logview-filter-edit--hint-comment text))) + (let* ((parent logview-filter-edit--parent-buffer) + (windows logview-filter-edit--window-configuration) + (do-quit (lambda () + (kill-buffer) + (switch-to-buffer parent) + (set-window-configuration windows)))) + (if logview-filter-edit--editing-views + (progn (when save + (let ((new-views (save-excursion + (goto-char 1) + (logview--parse-view-definitions t)))) + (if logview-filter-edit--editing-views-for-submode + (let ((combined-views (nreverse new-views))) + (dolist (view (logview--views)) + (unless (equal (plist-get view :submode) logview-filter-edit--editing-views-for-submode) + (push view combined-views))) + (setq logview--views (nreverse combined-views))) + (setq logview--views new-views)))) + (funcall do-quit) + ;; This takes effect only after quitting. + (logview--update-mode-name)) + (let ((filters (when save + (buffer-substring-no-properties 1 (1+ (buffer-size)))))) + (funcall do-quit) + (when save + (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)))))) + +(defun logview-filter-edit--initialize-text (&optional filters-text) (delete-region 1 (1+ (buffer-size))) - (insert text) - (unless (bolp) - (insert "\n")) + (if logview-filter-edit--editing-views + (progn (insert logview-filter-edit--views-hint-comment) + (logview--insert-view-definitions (when logview-filter-edit--editing-views-for-submode + (lambda (view) (string= (plist-get view :submode) + logview-filter-edit--editing-views-for-submode))))) + (unless (string-prefix-p logview-filter-edit--filters-hint-comment filters-text) + (insert logview-filter-edit--filters-hint-comment)) + (insert filters-text) + (unless (bolp) + (insert "\n"))) ;; Put cursor at the first filter beginning if possible. (goto-char 1) - (logview--iterate-filter-lines (lambda (type _line-begin begin _end) - (if (member type logview--valid-filter-prefixes) - (progn (goto-char begin) nil) - t))) + (logview--iterate-filter-buffer-lines (lambda (type _line-begin begin _end) + (if (member type logview--valid-filter-prefixes) + (progn (goto-char begin) nil) + t))) (set-buffer-modified-p nil)) (defun logview-filter-edit--font-lock-region (region-begin region-end &optional _old-length) @@ -2236,10 +2514,25 @@ Optional third argument is to make the function suitable for (while (and (not (bobp)) (looking-at "\.\. ")) (forward-line -1)) - (logview--iterate-filter-lines + (logview--iterate-filter-buffer-lines (lambda (type line-begin begin end) (cond ((null type) - (put-text-property begin end 'face 'error)) + (unless (when logview-filter-edit--editing-views + (save-excursion + (goto-char line-begin) + (cond ((looking-at logview--view-header-regexp) + (put-text-property (match-beginning 1) (match-end 1) 'face 'font-lock-keyword-face) + (put-text-property (match-beginning 2) (match-end 2) 'face 'font-lock-function-name-face) + t) + ((looking-at logview--view-submode-regexp) + (put-text-property (match-beginning 1) (match-end 1) 'face 'font-lock-keyword-face) + (let ((submode-name (match-string-no-properties 2))) + (put-text-property (match-beginning 2) (match-end 2) 'face (if (or (assoc submode-name logview-std-submodes) + (assoc submode-name logview-additional-submodes)) + 'font-lock-variable-name-face + 'error))) + t)))) + (put-text-property begin end 'face 'error))) ((string= type "#") (put-text-property begin end 'face 'font-lock-comment-face)) ((string= type "") @@ -2268,6 +2561,10 @@ Optional third argument is to make the function suitable for (< (point) region-end)))))))) +(add-hook 'kill-emacs-hook 'logview--kill-emacs-hook) +(run-with-idle-timer 30 t 'logview--save-views-if-needed) + + (provide 'logview) ;;; logview.el ends here