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

    Add view quick access indices to further simplify switching between views.
---
 README.md  |  10 ++++
 logview.el | 198 +++++++++++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 156 insertions(+), 52 deletions(-)

diff --git a/README.md b/README.md
index a38276cd8e..b3c04c1c15 100644
--- a/README.md
+++ b/README.md
@@ -172,8 +172,13 @@ See [more detailed description below](#views-explained).
 * 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)
+* Assign a quick access index to the current view: `V i`
 * Delete a view by name: `V d`
 
+You can also switch to views using their quick access index:
+`M-0`..`M-9` or e.g. `1 4 v` (for quick access index 14).  Prefix
+argument works also for `V n` and `V h`.
+
 #### Explicitly hide or show individual entries
 
 * Hide one entry: `h`
@@ -335,6 +340,11 @@ 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.
 
+To make choosing views even easier, you can optionally assign quick
+access indices to views.  For this, activate a view normally (or have
+it just saved), type `V i` and enter a number, say 3.  After this, the
+view can be quickly activated again by typing `M-3` or `3 v`.
+
 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
diff --git a/logview.el b/logview.el
index c53e47bc92..9c5a589df6 100644
--- a/logview.el
+++ b/logview.el
@@ -674,8 +674,9 @@ this face is used."
   "# 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))
+(defconst logview--view-header-regexp  (rx bol (group "view")    (1+ " ") 
(group (1+ nonl))          eol))
+(defconst logview--view-submode-regexp (rx bol (group "submode") (1+ " ") 
(group (1+ nonl))          eol))
+(defconst logview--view-index-regexp   (rx bol (group "index")   (1+ " ") 
(group (? "-") (1+ digit)) eol))
 
 
 (defvar logview--cheat-sheet
@@ -726,7 +727,11 @@ this face is used."
      (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"))
+     (logview-assign-quick-access-index                                      
"Assign a quick access index to the current view")
+     (logview-delete-view                                                    
"Delete a view")
+     "You can also switch to a view by its quick access index: 
\\[logview-switch-to-view-by-index <0>]..\\[logview-switch-to-view-by-index 
<9>].
+For larger indices use prefix argument, e.g.: \\[digit-argument <1>] 
\\[digit-argument <4>] \\[logview-switch-to-view].  This also
+works for \\[logview-set-navigation-view] and 
\\[logview-highlight-view-entries] commands.")
     ("Explicitly hide or show entries"
      (logview-hide-entry                                                     
"Hide entry")
      (logview-hide-region-entries                                            
"Hide entries in the region")
@@ -957,6 +962,7 @@ that the line is not the first in the buffer."
                        ("V S" logview-save-filters-as-global-view)
                        ("V e" logview-edit-submode-views)
                        ("V E" logview-edit-all-views)
+                       ("V i" logview-assign-quick-access-index)
                        ("V d" logview-delete-view)
                        ;; Explicit entry hiding/showing commands.
                        ("h"   logview-hide-entry)
@@ -1004,6 +1010,8 @@ that the line is not the first in the buffer."
                        ;; 'suppress-keymap' already.
                        ("u"   universal-argument)))
       (define-key map (kbd (car binding)) (cadr binding)))
+    (dotimes (k 10)
+      (define-key map (kbd (format "M-%d" k)) 
'logview-switch-to-view-by-index))
     map))
 
 (defvar logview-mode-inactive-map
@@ -1519,24 +1527,55 @@ entries and cancel any narrowing restrictions."
 
 (defun logview-switch-to-view (view)
   "Switch to a previously defined view.
-Interactively, read the view name from the minibuffer."
-  (interactive (list (logview--choose-view "Switch to view: ")))
+Argument VIEW can either be a string (view name) or a number, in
+which case view with that index is activated.
+
+If called interactively with a prefix argument, use its numeric
+value as quick access index.  Otherwise, read the view name from
+the minibuffer."
+  (interactive (list (logview--choose-view "Switch to view: " 
current-prefix-arg)))
   (setq logview--current-filter-text (plist-get (logview--find-view view) 
:filters))
   (logview--parse-filters))
 
+(defun logview-switch-to-view-by-index ()
+  "Switch to a view by its quick access index.
+This command must be bound to a key with a numeric values,
+possibly with modifiers, e.g. `3' or `M-3'.
+
+It is only for interactive use.  Non-interactively, use
+`logview-switch-to-view' instead."
+  (interactive)
+  (let* ((char  (if (integerp last-command-event)
+                   last-command-event
+                 (get last-command-event 'ascii-character)))
+        (index (- (logand char #x7f) ?0)))
+    (unless (<= 0 index 9)
+      (user-error "This command must invoked by a numeric key, possibly with 
modifiers"))
+    (logview-switch-to-view index)))
+
 (defun logview-set-navigation-view (view)
   "Set a view to be used for navigation.
-Interactively, read the view name from the minibuffer.
+Argument VIEW can either be a string (view name) or a number, in
+which case view with that index is activated.
+
+If called interactively with a prefix argument, use its numeric
+value as quick access index.  Otherwise, read the view name from
+the minibuffer.
 
 Navigation view filters are not active in the normal sense, but
 you can use `\\<logview-mode-map>\\[logview-next-navigation-view-entry]' and 
`\\<logview-mode-map>\\[logview-previous-navigation-view-entry]' keys to move 
across its entries."
-  (interactive (list (logview--choose-view "Navigate through view: ")))
+  (interactive (list (logview--choose-view "Navigate through view: " 
current-prefix-arg)))
   (setq logview--navigation-view-name (plist-get (logview--find-view view) 
:name)))
 
 (defun logview-highlight-view-entries (view)
   "Set a view to be used for entry highlighting.
-Interactively, read the view name from the minibuffer."
-  (interactive (list (logview--choose-view "Highlight entries of a view: ")))
+Argument VIEW can either be a string (view name) or a number, in
+which case view with that index is activated.
+
+If called interactively with a prefix argument, use its numeric
+value as quick access index.  Otherwise, read the view name from
+the minibuffer."
+  (interactive (list (logview--choose-view "Highlight entries of a view: " 
current-prefix-arg)))
   (logview--do-highlight-view-entries (plist-get (logview--find-view view) 
:name)))
 
 (defun logview-unhighlight-view-entries ()
@@ -1568,23 +1607,51 @@ minibuffer."
   (interactive)
   (logview--do-edit-views nil))
 
+(defun logview-assign-quick-access-index (index)
+  (interactive (list (when (logview--current-view)
+                       ;; Of course `read-number' insists on the default value 
being a
+                       ;; number and also stuffs it into the prompt.  Have to 
write our
+                       ;; own, wonderful...
+                       (let (index)
+                         (while (let ((string (read-from-minibuffer "View 
quick access index (empty for none): ")))
+                                  (cond ((equal string "")
+                                         nil)
+                                        ((integerp (ignore-errors (read 
string)))
+                                         (setq index (string-to-number string))
+                                         nil)
+                                        (t
+                                         (message "Please enter a number")
+                                        (sit-for 1)
+                                         t))))
+                         index))))
+  (let ((view (logview--current-view)))
+    (unless view
+      (user-error "Activate a view first"))
+    (plist-put view :index index)
+    (setq logview--views-need-saving t)
+    (logview--update-mode-name)))
+
 (defun logview-delete-view (view)
   "Delete a view definition.
-Interactively, read the view name from the minibuffer."
+Interactively, read the view name from the minibuffer.  Views
+cannot be deleted using their quick access indices."
+  ;; Intentionally not supporting prefix argument here: would be too 
error-prone.
   (interactive (list (logview--choose-view "Delete view: ")))
   (setq logview--views             (delq (logview--find-view view) 
(logview--views))
         logview--views-need-saving t)
   (logview--after-updating-view-definitions)
   (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"))
-    (logview--completing-read prompt defined-names nil t nil 
'logview--view-name-history)))
+(defun logview--choose-view (prompt &optional prefix-arg-value)
+  (if prefix-arg-value
+      (prefix-numeric-value prefix-arg-value)
+    (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"))
+      (logview--completing-read prompt defined-names nil t nil 
'logview--view-name-history))))
 
 (defalias 'logview--format-message (if (fboundp 'format-message) 
'format-message #'format))
 
@@ -2135,7 +2202,7 @@ These are:
               (if (listp entry)
                   (insert "  " (logview--help-format-keys entry "[1-5]" 
keys-width)
                           "  " (logview--help-substitute-keys (car (last 
entry))) "\n")
-                (insert "\n  " (logview--help-substitute-keys entry) 
"\n")))))))
+                (insert "\n  " (replace-regexp-in-string "\n" "\n  " 
(logview--help-substitute-keys entry)) "\n")))))))
     (goto-char 1)
     (help-mode)
     (let ((map (make-sparse-keymap)))
@@ -2454,14 +2521,15 @@ returns non-nil."
     ;; not only in memory, but also on disk.  We use `extmap' to create and 
read the cache
     ;; file.  If `datetime' reports a different locale database version, cache 
is
     ;; discarded.
-    (let ((cache-file              (ignore-errors (extmap-init 
logview-cache-filename)))
-          (locale-database-version (if (fboundp 
#'datetime-locale-database-version) (with-no-warnings 
(datetime-locale-database-version)) 0)))
+    (let* ((cache-filename          (locate-user-emacs-file 
"logview-cache.extmap"))
+           (cache-file              (ignore-errors (extmap-init 
cache-filename)))
+           (locale-database-version (if (fboundp 
#'datetime-locale-database-version) (with-no-warnings 
(datetime-locale-database-version)) 0)))
       (when cache-file
         (let ((cached-externally (extmap-get cache-file 'timestamp-formats t)))
           (when (and cached-externally (equal (extmap-get cache-file 
'locale-database-version t) locale-database-version))
             (setq logview--all-timestamp-formats-cache (extmap-get cache-file 
'timestamp-formats t)))))
       (if logview--all-timestamp-formats-cache
-          (logview--internal-log "Logview: loaded locale timestamp formats 
from `%s'" logview-cache-filename)
+          (logview--internal-log "Logview: loaded locale timestamp formats 
from `%s'" cache-filename)
         (let ((start-time (float-time))
               (patterns (make-hash-table :test 'equal :size 1000))
               (uniques  (make-hash-table :test 'equal :size 1000)))
@@ -2501,8 +2569,8 @@ returns non-nil."
                    uniques)
           (logview--internal-log "Logview/datetime: built list of %d timestamp 
regexps in %.3f s" (hash-table-count uniques) (- (float-time) start-time))
           (ignore-errors
-            (extmap-from-alist logview-cache-filename 
`((locale-database-version . ,locale-database-version)
-                                                        (timestamp-formats     
  . ,logview--all-timestamp-formats-cache))
+            (extmap-from-alist cache-filename `((locale-database-version . 
,locale-database-version)
+                                                (timestamp-formats       . 
,logview--all-timestamp-formats-cache))
                                :overwrite t))))))
   logview--all-timestamp-formats-cache)
 
@@ -2660,20 +2728,25 @@ See `logview--iterate-entries-forward' for details."
 
 
 (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)
+  (let ((view (logview--current-view)))
+    (setq mode-name (if view
+                        (if (plist-get view :index)
+                            (format "Logview/%s [%s]:%d" logview--submode-name 
(plist-get view :name) (plist-get view :index))
+                          (format "Logview/%s [%s]" logview--submode-name 
(plist-get view :name)))
                       (format "Logview/%s" logview--submode-name)))))
 
+(defun logview--current-view ()
+  (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 view)))
+    (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 view))))))
+
 (defun logview--update-invisibility-spec ()
   (let ((invisibility-spec (list logview--hidden-details-symbol 
logview--hidden-entry-symbol logview--filtered-symbol)))
     (when logview--hide-all-details
@@ -2936,16 +3009,27 @@ This list is preserved across Emacs session in
   logview--views)
 
 (defun logview--find-view (view &optional internal)
-  (if (stringp view)
+  (if (or (stringp view) (integerp view))
       (let ((all-views (logview--views))
-            result)
+            matches)
         (while all-views
           (let ((candidate (pop all-views)))
-            (when (and (string= (plist-get candidate :name) view)
+            (when (and (if (stringp view)
+                           (string= (plist-get candidate :name) view)
+                         (equal (plist-get candidate :index) view))
                        (or (null (plist-get candidate :submode)) (string= 
(plist-get candidate :submode) logview--submode-name)))
-              (setq result    candidate
-                    all-views nil))))
-        (or result (error "Unknown view `%s'" view)))
+              (push candidate matches))))
+        (cond ((null matches)
+               (funcall (if internal #'error #'user-error)
+                        (if (stringp view) "Unknown view `%s'" "There is no 
view with quick access index %d") view))
+              ((cdr matches)
+               (apply (if internal #'error #'user-error)
+                      (if (stringp view)
+                          `("There are at least two views named `%s'" ,view)
+                        `("There are at least two views with quick access 
index %d (`%s' and `%s')"
+                          ,view ,(plist-get (car matches) :name) ,(plist-get 
(cadr matches) :name)))))
+              (t
+               (car matches))))
     (if (and (listp view) (stringp (plist-get view :name)))
         view
       (unless internal
@@ -2956,24 +3040,28 @@ This list is preserved across Emacs session in
     (let (views
           pending-name
           pending-submode
+          pending-index
           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)))
+                       (push `(:name    ,pending-name
+                               :submode ,pending-submode
+                               ,@(when pending-index `(:index ,pending-index))
+                               :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 pending-name    (match-string-no-properties 2)
+                         pending-submode nil
+                         pending-index   nil)
+                   (while (progn (forward-line)
+                                 (cond ((looking-at 
logview--view-submode-regexp)
+                                        (setq pending-submode 
(match-string-no-properties 2)))
+                                       ((looking-at logview--view-index-regexp)
+                                        (setq pending-index (string-to-number 
(match-string-no-properties 2)))))))
                    (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? ")
@@ -2989,6 +3077,8 @@ This list is preserved across Emacs session in
       (insert "view " (plist-get view :name) "\n")
       (when (plist-get view :submode)
         (insert "submode " (plist-get view :submode) "\n"))
+      (when (plist-get view :index)
+        (insert "index " (number-to-string (plist-get view :index)) "\n"))
       (insert (plist-get view :filters))
       (unless (bolp)
         (insert "\n")))))
@@ -3286,7 +3376,7 @@ This list is preserved across Emacs session in
           (forward-line 0)
           ;; Never try to parse from the middle of a multiline filter.
           (while (and (not (bobp))
-                      (looking-at "\.\. "))
+                      (looking-at "\\.\\. "))
             (forward-line -1))
           (logview--iterate-filter-buffer-lines
            (lambda (type line-begin begin end)
@@ -3305,6 +3395,10 @@ This list is preserved across Emacs session in
                                                                                
                             (assoc submode-name logview-additional-submodes))
                                                                                
                         'font-lock-variable-name-face
                                                                                
                       'error)))
+                                       t)
+                                      ((looking-at logview--view-index-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-constant-face)
                                        t))))
                       (put-text-property begin end 'face 'error)))
                    ((string= type "#")

Reply via email to