emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/rt-liberation f682e7b 68/68: Merge branch 'master' into


From: Yoni Rabkin
Subject: [elpa] externals/rt-liberation f682e7b 68/68: Merge branch 'master' into externals/rt-liberation
Date: Wed, 16 Dec 2020 12:11:18 -0500 (EST)

branch: externals/rt-liberation
commit f682e7bfac617da558a5a342553e325150928b00
Merge: 6b062b7 d964a92
Author: Yoni Rabkin <yoni@rabkins.net>
Commit: Yoni Rabkin <yoni@rabkins.net>

    Merge branch 'master' into externals/rt-liberation
---
 Makefile                  |    2 +
 NEWS                      |    9 +-
 doc/developer-release.txt |   39 +-
 doc/rt-liber.texinfo      |   99 ++---
 rt-liber.info             |  209 +++++----
 rt-liberation-gnus.el     |   12 +-
 rt-liberation-multi.el    |    2 +-
 rt-liberation-report.el   |    2 +-
 rt-liberation-rest.el     |   40 +-
 rt-liberation-storage.el  |    2 +-
 rt-liberation-update.el   |    2 +-
 rt-liberation.el          | 1059 +++++++++++++++++++++++++++++++--------------
 12 files changed, 952 insertions(+), 525 deletions(-)

diff --git a/Makefile b/Makefile
index 8f3e500..9005179 100644
--- a/Makefile
+++ b/Makefile
@@ -34,3 +34,5 @@ all: $(TARGET)
 
 clean:
        -rm -f *~ *.elc
+
+neat: all clean
diff --git a/NEWS b/NEWS
index 9a75035..2490ea3 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,13 @@
-New in version 1.0:
+New in version ...
+
+    * Support getting REST credentials from auth-source.
+
+
+New in versions 1.0 to 1.31:
 
     * Upgrade to GPLv3 (with the kind permission of the people from
       Best Practical).
 
     * Remove the CLI interface.
+
+    * Make sure the manual appears in ELPA.
diff --git a/doc/developer-release.txt b/doc/developer-release.txt
index e976f0b..e15282c 100644
--- a/doc/developer-release.txt
+++ b/doc/developer-release.txt
@@ -1,18 +1,41 @@
 -*- outline -*-
 
-* Notes for preparing a release for rt-liberation
+This is an outline of how to make a release for rt-liberation via GNU
+ELPA.
 
+* compile
+Check for and correct compile-time errors and warnings.
 
-** Version
+* version
 In order for the ELPA system to trigger a release the version number
 in the comment header of rt-liberation.el must be incremented.
 
+* Good News
+Update the NEWS file to tell all of the people the Good News.
 
-** Info
-After each manual update the info file needs to be copied into the
-root of the project so that ELPA can pick it up.
+* documentation
+Update the manual, compile it, and update the copy of the info file in
+the root directory so that ELPA can install it.
 
+Compile an HTML version of the manual and update it on the Website:
 
-** ELPA
-Updates to the Savannah repository that have been tested can be pushed
-to the ELPA repository at externals/rt-liberation.
+    $ cvs commit -m "update manual" rt-liber.html
+
+    $ cvs commit -m "update website" index.html
+
+* push
+Push these updates to the git repo.
+
+* tag
+Tag the release with the ELPA version number:
+
+    $ git tag -a 2.00 -m "2.00"
+
+Then push that tag to the VCS:
+ 
+    $ git push --tags origin "2.00"
+
+* ELPA
+Push the changes to externals/rt-liberation on elpa.git with:
+
+    $ git push elpa elpa:refs/heads/externals/rt-liberation
diff --git a/doc/rt-liber.texinfo b/doc/rt-liber.texinfo
index b8cb1f5..bc2a095 100644
--- a/doc/rt-liber.texinfo
+++ b/doc/rt-liber.texinfo
@@ -48,7 +48,8 @@ This is the Manual for the rt-liberation system
 
 @menu
 * Introduction::                Introduction to rt-liberation.
-* Installation::                Setup rt-liberation to work on the system.
+* Installation::                Install rt-liberation on the system.
+* Configuration::               Setup rt-liberation to work on the system.
 
 Using rt-liberation
 * Queries::                     Retrieve particular tickets from the server.
@@ -117,10 +118,12 @@ operations on the tickets.
 @chapter Installation
 @cindex installation
 
-rt-liberation is available via GNU ELPA.
+rt-liberation is available via GNU ELPA. Invoke @kbd{M-x
+list-packages} and choose to install the rt-liberation package.
 
-If you install rt-liberation manually instead you'll need to tell
-Emacs where to find it, and tell Emacs to load the package:
+If you install rt-liberation manually, by copying the code to your
+machine, instead you'll need to tell Emacs where to find it, and then
+tell Emacs to load the package:
 
 @lisp
 (add-to-list 'load-path "/PATH/TO/rt-liberation/")
@@ -131,6 +134,11 @@ Emacs where to find it, and tell Emacs to load the package:
 @end lisp
 
 
+@c --------------------------------------------------
+@node Configuration
+@chapter Configuration
+@cindex configuration
+
 rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber
 file, or similar.
 
@@ -140,6 +148,25 @@ Tell rt-liberation where to find the RT server's REST 
interface:
 (setq rt-liber-rest-url "rt.example.org")
 @end lisp
 
+In order to authenticate with the RT server instance you need to
+provide credentials. rt-liberation looks for these in the variables
+@var{rt-liber-rest-username} and @var{rt-liber-rest-password}. You can
+set these directly:
+
+@lisp
+(setq rt-liber-rest-username "someuser"
+      rt-liber-rest-password "somepassword")
+@end lisp
+
+You can also leave these values unset (@code{nil}), in which case
+rt-liberation will look for the credentials in a Netrc file via the
+auth-source library (see: @xref{Top,,, auth, Emacs auth-source}),
+under the machine name "rt-liberation":
+
+@example
+machine rt-liberation login someuser password somepassword
+@end example
+
 rt-liberation can issue a command to ``take'' a ticket (that is,
 assign it to yourself). For this the variable @var{rt-liber-username}
 must be set:
@@ -148,9 +175,9 @@ must be set:
 (setq rt-liber-username "someuser")
 @end lisp
 
-rt-liberation can also launch a Web browser to visit a ticket. For
-that to work the base URL needs to be set in
-@var{rt-liber-base-url}. For example:
+rt-liberation can launch a Web browser to visit a ticket. For that to
+work the base URL needs to be set in @var{rt-liber-base-url}. For
+example:
 
 (setq rt-liber-base-url "https://rt.foo.org/";)
 
@@ -603,10 +630,8 @@ buffers will be created displaying the query results and 
named
 @chapter Ticket Viewer
 @cindex ticket viewer
 
-The ticket viewer is an interface for viewing the contents of a
-ticket. It provides font-locking to make reading the contents easier
-via @var{rt-liber-viewer-font-lock-keywords} and a number of
-key-bindings.
+The ticket viewer is an interface for viewing the contents of a ticket
+and for sending answers.
 
 The ticket viewer provides key-bindings to help compose emails to send
 to the RT email interface. The key-bindings for composing email
@@ -615,34 +640,26 @@ them depends on the email-backend system you have 
installed into
 rt-liberation. @file{rt-liberation-gnus.el} provides integration with
 Gnus, @xref{Gnus Integration}.
 
-Setting @var{rt-liber-jump-to-latest} to `t' will cause the viewer to
-automatically scroll to the latest comment in a ticket when that
-ticket is visited. By default @var{rt-liber-jump-to-latest} is set to
-`nil'.
-
-When in the ticket viewer buffer, invoking
-@dfn{rt-liber-viewer-take-ticket} will ``take'' the ticket.
-
 @table @kbd
 
 @item q
 @kindex q (ticket viewer)
-@findex rt-liber-viewer-mode-quit
+@findex rt-liber-viewer2-mode-quit
 Bury the ticket viewer buffer.
 
 @item n
 @kindex n (ticket viewer)
-@findex rt-liber-next-section-in-viewer
-Move point to the next section in ticket.
+@findex rt-liber-viewer2-next-section-in
+Move to the next section in ticket.
 
 @item N
 @kindex N (ticket viewer)
-@findex rt-liber-jump-to-latest-correspondence
-Move point to the newest correspondence section, if any.
+@findex rt-liber-viewer2-last-section-in
+Move to the last section.
 
 @item p
 @kindex p (ticket viewer)
-@findex rt-liber-previous-section-in-viewer
+@findex rt-liber-viewer2-previous-section-in
 Move point to the previous section in ticket.
 
 @item V
@@ -650,43 +667,15 @@ Move point to the previous section in ticket.
 @findex rt-liber-viewer-visit-in-browser
 Visit the current ticket in a Web browser.
 
-@item m
-@kindex m (ticket viewer)
-@findex rt-liber-viewer-answer
-Compose an answer to the current ticket.
-
 @item M
 @kindex M (ticket viewer)
-@findex rt-liber-viewer-answer-this
+@findex rt-liber-viewer2-answer
 Compose an answer to the current ticket. The content section around
 point will be inserted into the email body and commented out.
 
-@item t
-@kindex t (ticket viewer)
-@findex rt-liber-viewer-answer-provisionally
-Compose a provisional answer to the current ticket.
-
-@item T
-@kindex t (ticket viewer)
-@findex rt-liber-viewer-answer-provisionally-this
-Compose a provisional answer to the current ticket. The content
-section around point will be inserted into the email body and
-commented out.
-
-@item F
-@kindex F (ticket viewer)
-@findex rt-liber-viewer-answer-verbatim-this
-Compose an answer to the current ticket. The content section around
-point will be inserted into the email body verbatim.
-
-@item c
-@kindex c (ticket viewer)
-@findex rt-liber-viewer-comment
-Compose a comment for the current ticket.
-
 @item C
 @kindex C (ticket viewer)
-@findex rt-liber-viewer-comment-this
+@findex rt-liber-viewer2-comment
 Comment on the ticket using the current context
 
 @item g
diff --git a/rt-liber.info b/rt-liber.info
index f712b66..c2df1d8 100644
--- a/rt-liber.info
+++ b/rt-liber.info
@@ -36,7 +36,8 @@ Foundation, Inc.
 * Menu:
 
 * Introduction::                Introduction to rt-liberation.
-* Installation::                Setup rt-liberation to work on the system.
+* Installation::                Install rt-liberation on the system.
+* Configuration::               Setup rt-liberation to work on the system.
 
 Using rt-liberation
 * Queries::                     Retrieve particular tickets from the server.
@@ -95,42 +96,64 @@ browsing the resulting tickets, viewing the tickets' 
contents and
 performing operations on the tickets.
 
 
-File: rt-liber.info,  Node: Installation,  Next: Queries,  Prev: Introduction, 
 Up: Top
+File: rt-liber.info,  Node: Installation,  Next: Configuration,  Prev: 
Introduction,  Up: Top
 
 2 Installation
 **************
 
-rt-liberation is available via GNU ELPA.
+rt-liberation is available via GNU ELPA. Invoke 'M-x list-packages' and
+choose to install the rt-liberation package.
 
-   If you install rt-liberation manually instead you'll need to tell
-Emacs where to find it, and tell Emacs to load the package:
+   If you install rt-liberation manually, by copying the code to your
+machine, instead you'll need to tell Emacs where to find it, and then
+tell Emacs to load the package:
 
      (add-to-list 'load-path "/PATH/TO/rt-liberation/")
 
      (require 'rt-liberation)
 
-   rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber
+
+File: rt-liber.info,  Node: Configuration,  Next: Queries,  Prev: 
Installation,  Up: Top
+
+3 Configuration
+***************
+
+rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber
 file, or similar.
 
    Tell rt-liberation where to find the RT server's REST interface:
 
      (setq rt-liber-rest-url "rt.example.org")
 
+   In order to authenticate with the RT server instance you need to
+provide credentials.  rt-liberation looks for these in the variables
+RT-LIBER-REST-USERNAME and RT-LIBER-REST-PASSWORD.  You can set these
+directly:
+
+     (setq rt-liber-rest-username "someuser"
+           rt-liber-rest-password "somepassword")
+
+   You can also leave these values unset ('nil'), in which case
+rt-liberation will look for the credentials in a Netrc file via the
+auth-source library (see: *Note (auth)Top::), under the machine name
+"rt-liberation":
+
+     machine rt-liberation login someuser password somepassword
+
    rt-liberation can issue a command to "take" a ticket (that is, assign
 it to yourself).  For this the variable RT-LIBER-USERNAME must be set:
 
      (setq rt-liber-username "someuser")
 
-   rt-liberation can also launch a Web browser to visit a ticket.  For
-that to work the base URL needs to be set in RT-LIBER-BASE-URL.  For
-example:
+   rt-liberation can launch a Web browser to visit a ticket.  For that
+to work the base URL needs to be set in RT-LIBER-BASE-URL.  For example:
 
    (setq rt-liber-base-url "https://rt.foo.org/";)
 
 
-File: rt-liber.info,  Node: Queries,  Next: Ticket Browser,  Prev: 
Installation,  Up: Top
+File: rt-liber.info,  Node: Queries,  Next: Ticket Browser,  Prev: 
Configuration,  Up: Top
 
-3 Queries
+4 Queries
 *********
 
 A typical RT server is meant to manage a large amount of tickets.  Much
@@ -148,7 +171,7 @@ RT's TicketSQL language.
 
 File: rt-liber.info,  Node: Query Compiler,  Next: Query Language,  Up: Queries
 
-3.1 Query Compiler
+4.1 Query Compiler
 ==================
 
 In order to browse and view tickets a list of needs to be requested from
@@ -164,7 +187,7 @@ number of TicketSQL tokens.
 
 File: rt-liber.info,  Node: Query Language,  Prev: Query Compiler,  Up: Queries
 
-3.2 Query Language
+4.2 Query Language
 ==================
 
 rt-liberation's Sexp-based query language covers a portion of the
@@ -232,7 +255,7 @@ in function calls:
 
 File: rt-liber.info,  Node: Ticket Browser,  Next: Ticket Viewer,  Prev: 
Queries,  Up: Top
 
-4 Ticket Browser
+5 Ticket Browser
 ****************
 
 The ticket browser is a special buffer which provides a convenient
@@ -324,7 +347,7 @@ string, the following is equivalent:
 
 File: rt-liber.info,  Node: Ticket Browser Display,  Next: Ticket Browser 
Sorting,  Up: Ticket Browser
 
-4.1 Ticket Browser Display
+5.1 Ticket Browser Display
 ==========================
 
 The ticket browser displays the tickets in the browser by calling
@@ -384,7 +407,7 @@ RT-LIBER-BROWSER-PRIORITY-CUTOFF
 
 File: rt-liber.info,  Node: Ticket Browser Sorting,  Next: Ticket Browser 
Filtering,  Prev: Ticket Browser Display,  Up: Ticket Browser
 
-4.2 Ticket Browser Sorting
+5.2 Ticket Browser Sorting
 ==========================
 
 The tickets in the browser are displayed by default in reverse
@@ -431,7 +454,7 @@ functions to perform comparisons between ticket objects:
 
 File: rt-liber.info,  Node: Ticket Browser Filtering,  Next: Multiple Ticket 
Browsers,  Prev: Ticket Browser Sorting,  Up: Ticket Browser
 
-4.3 Ticket Browser Filtering
+5.3 Ticket Browser Filtering
 ============================
 
 The Ticket Browser can also filter out (that is, not display) certain
@@ -469,7 +492,7 @@ ticket is to be filtered.
 
 File: rt-liber.info,  Node: Multiple Ticket Browsers,  Prev: Ticket Browser 
Filtering,  Up: Ticket Browser
 
-4.4 Multiple Ticket Browsers
+5.4 Multiple Ticket Browsers
 ============================
 
 It is sometimes useful to rename the ticket browser buffer to something
@@ -503,12 +526,11 @@ supervisor*" and "*new tickets*" respectively:
 
 File: rt-liber.info,  Node: Ticket Viewer,  Next: Gnus Integration,  Prev: 
Ticket Browser,  Up: Top
 
-5 Ticket Viewer
+6 Ticket Viewer
 ***************
 
-The ticket viewer is an interface for viewing the contents of a ticket.
-It provides font-locking to make reading the contents easier via
-RT-LIBER-VIEWER-FONT-LOCK-KEYWORDS and a number of key-bindings.
+The ticket viewer is an interface for viewing the contents of a ticket
+and for sending answers.
 
    The ticket viewer provides key-bindings to help compose emails to
 send to the RT email interface.  The key-bindings for composing email
@@ -517,21 +539,14 @@ depends on the email-backend system you have installed 
into
 rt-liberation.  'rt-liberation-gnus.el' provides integration with Gnus,
 *Note Gnus Integration::.
 
-   Setting RT-LIBER-JUMP-TO-LATEST to 't' will cause the viewer to
-automatically scroll to the latest comment in a ticket when that ticket
-is visited.  By default RT-LIBER-JUMP-TO-LATEST is set to 'nil'.
-
-   When in the ticket viewer buffer, invoking
-"rt-liber-viewer-take-ticket" will "take" the ticket.
-
 'q'
      Bury the ticket viewer buffer.
 
 'n'
-     Move point to the next section in ticket.
+     Move to the next section in ticket.
 
 'N'
-     Move point to the newest correspondence section, if any.
+     Move to the last section.
 
 'p'
      Move point to the previous section in ticket.
@@ -539,29 +554,11 @@ is visited.  By default RT-LIBER-JUMP-TO-LATEST is set to 
'nil'.
 'V'
      Visit the current ticket in a Web browser.
 
-'m'
-     Compose an answer to the current ticket.
-
 'M'
      Compose an answer to the current ticket.  The content section
      around point will be inserted into the email body and commented
      out.
 
-'t'
-     Compose a provisional answer to the current ticket.
-
-'T'
-     Compose a provisional answer to the current ticket.  The content
-     section around point will be inserted into the email body and
-     commented out.
-
-'F'
-     Compose an answer to the current ticket.  The content section
-     around point will be inserted into the email body verbatim.
-
-'c'
-     Compose a comment for the current ticket.
-
 'C'
      Comment on the ticket using the current context
 
@@ -580,7 +577,7 @@ is visited.  By default RT-LIBER-JUMP-TO-LATEST is set to 
'nil'.
 
 File: rt-liber.info,  Node: Gnus Integration,  Next: Tracking Updates,  Prev: 
Ticket Viewer,  Up: Top
 
-6 Gnus Integration
+7 Gnus Integration
 ******************
 
 The file 'rt-liberation-gnus.el' implements integration with Gnus for
@@ -635,7 +632,7 @@ the Viewer will be able to call into it, *Note Ticket 
Viewer::.
 
 File: rt-liber.info,  Node: Tracking Updates,  Next: Batch Operations,  Prev: 
Gnus Integration,  Up: Top
 
-7 Tracking Updates
+8 Tracking Updates
 ******************
 
 The functions in 'rt-liberation-update.el' help keep up with updates to
@@ -664,7 +661,7 @@ watch for updates.  For example:
 
 File: rt-liber.info,  Node: Batch Operations,  Next: Local Storage,  Prev: 
Tracking Updates,  Up: Top
 
-8 Batch Operations
+9 Batch Operations
 ******************
 
 The extension 'rt-liberation-multi.el' implements performing batch
@@ -697,8 +694,8 @@ through all of the marked tickets.
 
 File: rt-liber.info,  Node: Local Storage,  Next: Copying,  Prev: Batch 
Operations,  Up: Top
 
-9 Local Storage
-***************
+10 Local Storage
+****************
 
 'rt-liberation-storage.el' implements associating arbitrary ancillary
 data with tickets.  The data is stored locally and is not sent to the RT
@@ -730,7 +727,7 @@ can be extended to associate any arbitrary data with any 
ticket.
 
 File: rt-liber.info,  Node: Copying,  Next: The GNU FDL,  Prev: Local Storage, 
 Up: Top
 
-10 The GNU General Public License.
+11 The GNU General Public License.
 **********************************
 
                         Version 3, 29 June 2007
@@ -1446,7 +1443,7 @@ please read 
<https://www.gnu.org/licenses/why-not-lgpl.html>.
 
 File: rt-liber.info,  Node: The GNU FDL,  Next: Concept Index,  Prev: Copying, 
 Up: Top
 
-11 GNU Free Documentation License
+12 GNU Free Documentation License
 *********************************
 
                       Version 1.2, November 2002
@@ -1841,7 +1838,7 @@ File: rt-liber.info,  Node: The GNU FDL,  Next: Concept 
Index,  Prev: Copying,
      choose any version ever published (not as a draft) by the Free
      Software Foundation.
 
-11.1 ADDENDUM: How to use this License for your documents
+12.1 ADDENDUM: How to use this License for your documents
 =========================================================
 
 To use this License in a document you have written, include a copy of
@@ -1882,6 +1879,7 @@ Concept Index
 * Menu:
 
 * Batch Operations:                      Batch Operations.      (line 6)
+* configuration:                         Configuration.         (line 6)
 * FDL, GNU Free Documentation License:   The GNU FDL.           (line 6)
 * Gnus Integration:                      Gnus Integration.      (line 6)
 * installation:                          Installation.          (line 6)
@@ -1912,7 +1910,7 @@ Function Index
 * Menu:
 
 * revert-buffer:                         Ticket Browser.       (line 48)
-* revert-buffer <1>:                     Ticket Viewer.        (line 66)
+* revert-buffer <1>:                     Ticket Viewer.        (line 40)
 * rt-liber-browse-query:                 Ticket Browser.       (line 12)
 * rt-liber-browser-ancillary-text:       Local Storage.        (line 22)
 * rt-liber-browser-assign:               Ticket Browser.       (line 62)
@@ -1925,7 +1923,6 @@ Function Index
 * rt-liber-browser-resolve:              Ticket Browser.       (line 65)
 * rt-liber-browser-take-ticket-at-point: Ticket Browser.       (line 71)
 * rt-liber-display-ticket-at-point:      Ticket Browser.       (line 45)
-* rt-liber-jump-to-latest-correspondence: Ticket Viewer.       (line 31)
 * rt-liber-lex-lessthan-p:               Ticket Browser Sorting.
                                                                (line 15)
 * rt-liber-mark-ticket-at-point:         Batch Operations.     (line 18)
@@ -1934,27 +1931,23 @@ Function Index
 * rt-liber-multi-flag-as-spam-and-delete: Batch Operations.    (line 30)
 * rt-liber-multi-set-status-open:        Batch Operations.     (line 21)
 * rt-liber-multi-set-status-resolved:    Batch Operations.     (line 24)
-* rt-liber-next-section-in-viewer:       Ticket Viewer.        (line 28)
 * rt-liber-next-ticket-in-browser:       Ticket Browser.       (line 39)
-* rt-liber-previous-section-in-viewer:   Ticket Viewer.        (line 34)
 * rt-liber-previous-ticket-in-browser:   Ticket Browser.       (line 42)
 * rt-liber-time-lessthan-p:              Ticket Browser Sorting.
                                                                (line 30)
 * rt-liber-update:                       Tracking Updates.     (line 17)
-* rt-liber-viewer-answer:                Ticket Viewer.        (line 40)
-* rt-liber-viewer-answer-provisionally:  Ticket Viewer.        (line 48)
-* rt-liber-viewer-answer-provisionally-this: Ticket Viewer.    (line 51)
-* rt-liber-viewer-answer-this:           Ticket Viewer.        (line 43)
-* rt-liber-viewer-answer-verbatim-this:  Ticket Viewer.        (line 56)
-* rt-liber-viewer-comment:               Ticket Viewer.        (line 60)
-* rt-liber-viewer-comment-this:          Ticket Viewer.        (line 63)
-* rt-liber-viewer-mode-quit:             Ticket Viewer.        (line 25)
-* rt-liber-viewer-show-ticket-browser:   Ticket Viewer.        (line 75)
-* rt-liber-viewer-visit-in-browser:      Ticket Viewer.        (line 37)
+* rt-liber-viewer-show-ticket-browser:   Ticket Viewer.        (line 49)
+* rt-liber-viewer-visit-in-browser:      Ticket Viewer.        (line 29)
+* rt-liber-viewer2-answer:               Ticket Viewer.        (line 32)
+* rt-liber-viewer2-comment:              Ticket Viewer.        (line 37)
+* rt-liber-viewer2-last-section-in:      Ticket Viewer.        (line 23)
+* rt-liber-viewer2-mode-quit:            Ticket Viewer.        (line 17)
+* rt-liber-viewer2-next-section-in:      Ticket Viewer.        (line 20)
+* rt-liber-viewer2-previous-section-in:  Ticket Viewer.        (line 26)
 * scroll-down:                           Ticket Browser.       (line 77)
-* scroll-down <1>:                       Ticket Viewer.        (line 72)
+* scroll-down <1>:                       Ticket Viewer.        (line 46)
 * scroll-up:                             Ticket Browser.       (line 74)
-* scroll-up <1>:                         Ticket Viewer.        (line 69)
+* scroll-up <1>:                         Ticket Viewer.        (line 43)
 
 
 File: rt-liber.info,  Node: Variable Index,  Next: Keybinding Index,  Prev: 
Function Index,  Up: Top
@@ -1980,63 +1973,59 @@ Keybinding Index
 
 * a (ticket browser):                    Ticket Browser.       (line 62)
 * A (ticket browser):                    Local Storage.        (line 22)
-* c (ticket viewer):                     Ticket Viewer.        (line 60)
-* C (ticket viewer):                     Ticket Viewer.        (line 63)
+* C (ticket viewer):                     Ticket Viewer.        (line 37)
 * DEL (ticket browser):                  Ticket Browser.       (line 77)
-* DEL (ticket viewer):                   Ticket Viewer.        (line 72)
-* F (ticket viewer):                     Ticket Viewer.        (line 56)
+* DEL (ticket viewer):                   Ticket Viewer.        (line 46)
 * g (ticket browser):                    Ticket Browser.       (line 48)
 * G (ticket browser):                    Ticket Browser.       (line 51)
-* g (ticket viewer):                     Ticket Viewer.        (line 66)
-* h (ticket viewer):                     Ticket Viewer.        (line 75)
+* g (ticket viewer):                     Ticket Viewer.        (line 40)
+* h (ticket viewer):                     Ticket Viewer.        (line 49)
 * m (ticket browser):                    Ticket Browser.       (line 80)
 * M (ticket browser):                    Batch Operations.     (line 18)
-* m (ticket viewer):                     Ticket Viewer.        (line 40)
-* M (ticket viewer):                     Ticket Viewer.        (line 43)
+* M (ticket viewer):                     Ticket Viewer.        (line 32)
 * n (ticket browser):                    Ticket Browser.       (line 39)
-* n (ticket viewer):                     Ticket Viewer.        (line 28)
-* N (ticket viewer):                     Ticket Viewer.        (line 31)
+* n (ticket viewer):                     Ticket Viewer.        (line 20)
+* N (ticket viewer):                     Ticket Viewer.        (line 23)
 * o (ticket browser):                    Ticket Browser.       (line 68)
 * p (ticket browser):                    Ticket Browser.       (line 42)
 * P (ticket browser):                    Ticket Browser.       (line 83)
-* p (ticket viewer):                     Ticket Viewer.        (line 34)
+* p (ticket viewer):                     Ticket Viewer.        (line 26)
 * q (ticket browser):                    Ticket Browser.       (line 36)
-* q (ticket viewer):                     Ticket Viewer.        (line 25)
+* q (ticket viewer):                     Ticket Viewer.        (line 17)
 * r (ticket browser):                    Ticket Browser.       (line 65)
 * RET (ticket browser):                  Ticket Browser.       (line 45)
 * s (ticket browser):                    Ticket Browser.       (line 55)
 * S (ticket browser):                    Ticket Browser.       (line 58)
 * SPC (ticket browser):                  Ticket Browser.       (line 74)
-* SPC (ticket viewer):                   Ticket Viewer.        (line 69)
+* SPC (ticket viewer):                   Ticket Viewer.        (line 43)
 * t (ticket browser):                    Ticket Browser.       (line 71)
-* t (ticket viewer):                     Ticket Viewer.        (line 48)
-* t (ticket viewer) <1>:                 Ticket Viewer.        (line 51)
-* V (ticket viewer):                     Ticket Viewer.        (line 37)
+* V (ticket viewer):                     Ticket Viewer.        (line 29)
 
 
 
 Tag Table:
 Node: Top680
-Node: Introduction2956
-Node: Installation3563
-Node: Queries4542
-Node: Query Compiler5104
-Node: Query Language5745
-Node: Ticket Browser8184
-Node: Ticket Browser Display10656
-Node: Ticket Browser Sorting12647
-Node: Ticket Browser Filtering14597
-Node: Multiple Ticket Browsers16140
-Node: Ticket Viewer17309
-Node: Gnus Integration19481
-Node: Tracking Updates21769
-Node: Batch Operations22846
-Node: Local Storage24026
-Node: Copying25104
-Node: The GNU FDL62653
-Node: Concept Index85045
-Node: Function Index86626
-Node: Variable Index89976
-Node: Keybinding Index90352
+Node: Introduction3025
+Node: Installation3632
+Node: Configuration4139
+Node: Queries5451
+Node: Query Compiler6014
+Node: Query Language6655
+Node: Ticket Browser9094
+Node: Ticket Browser Display11566
+Node: Ticket Browser Sorting13557
+Node: Ticket Browser Filtering15507
+Node: Multiple Ticket Browsers17050
+Node: Ticket Viewer18219
+Node: Gnus Integration19478
+Node: Tracking Updates21766
+Node: Batch Operations22843
+Node: Local Storage24023
+Node: Copying25103
+Node: The GNU FDL62652
+Node: Concept Index85044
+Node: Function Index86698
+Node: Variable Index89683
+Node: Keybinding Index90059
 
 End Tag Table
diff --git a/rt-liberation-gnus.el b/rt-liberation-gnus.el
index eb0b262..789e482 100644
--- a/rt-liberation-gnus.el
+++ b/rt-liberation-gnus.el
@@ -1,4 +1,4 @@
-;;; rt-liberation-gnus.el --- Gnus integration for rt-liberation
+;;; rt-liberation-gnus.el --- Gnus integration for rt-liberation  -*- 
lexical-binding: t; -*-
 
 ;; Copyright (C) 2009-2014  Free Software Foundation, Inc.
 ;;
@@ -32,16 +32,6 @@
   :prefix "rt-liber-gnus-"
   :group 'rt-liber-gnus)
 
-(defcustom rt-liber-gnus-comment-address "no comment address set"
-  "*Email address for adding a comment."
-  :type 'string
-  :group 'rt-liber-gnus)
-
-(defcustom rt-liber-gnus-address "no reply address set"
-  "*Email address for replying to requestor."
-  :type 'string
-  :group 'rt-liber-gnus)
-
 (defcustom rt-liber-gnus-subject-name "no subject name set"
   "*Subject name to be included in email header."
   :type 'string
diff --git a/rt-liberation-multi.el b/rt-liberation-multi.el
index 8e4665a..d4a8602 100644
--- a/rt-liberation-multi.el
+++ b/rt-liberation-multi.el
@@ -1,4 +1,4 @@
-;;; rt-liberation-multi.el --- Emacs interface to RT
+;;; rt-liberation-multi.el --- Emacs interface to RT  -*- lexical-binding: t; 
-*-
 
 ;; Copyright (C) 2010, 2014  Free Software Foundation, Inc.
 ;;
diff --git a/rt-liberation-report.el b/rt-liberation-report.el
index 11d20c2..a60ae5b 100644
--- a/rt-liberation-report.el
+++ b/rt-liberation-report.el
@@ -1,4 +1,4 @@
-;;; rt-liberation-report.el --- Emacs interface to RT
+;;; rt-liberation-report.el --- Emacs interface to RT  -*- lexical-binding: t; 
-*-
 
 ;; Copyright (C) 2015  Free Software Foundation, Inc.
 ;;
diff --git a/rt-liberation-rest.el b/rt-liberation-rest.el
index 99f6ce9..a7eb076 100644
--- a/rt-liberation-rest.el
+++ b/rt-liberation-rest.el
@@ -1,4 +1,4 @@
-;;; rt-liberation-rest.el --- Interface to the RT REST API
+;;; rt-liberation-rest.el --- Interface to the RT REST API  -*- 
lexical-binding: t; -*-
 
 ;; Copyright (C) 2014-2015  Free Software Foundation, Inc.
 ;;
@@ -31,8 +31,12 @@
 
 (require 'url)
 (require 'url-util)
+(require 'auth-source)
 
 
+;;; ------------------------------------------------------------------
+;;; variables and constants
+;;; ------------------------------------------------------------------
 (defvar rt-liber-rest-debug-buffer-name "*rt-liber-rest debug log*"
   "Buffer name of debug capture.")
 
@@ -45,16 +49,19 @@
 (defvar rt-liber-rest-url ""
   "URL of RT installation.")
 
-(defvar rt-liber-rest-username ""
+(defvar rt-liber-rest-username nil
   "Username of RT account.")
 
-(defvar rt-liber-rest-password ""
+(defvar rt-liber-rest-password nil
   "Password of RT account.")
 
 (defvar rt-liber-rest-verbose-p t
   "If non-nil, be verbose about what's happening.")
 
 
+;;; ------------------------------------------------------------------
+;;; functions
+;;; ------------------------------------------------------------------
 (defun rt-liber-rest-write-debug (str)
   "Write to debug buffer."
   (when (not (stringp str))
@@ -65,6 +72,21 @@
       (goto-char (point-max))
       (insert str))))
 
+(defun rt-liber-rest-auth ()
+  "Try to get the REST credentials."
+  (if (and (stringp rt-liber-rest-username)
+          (stringp rt-liber-rest-password)
+          (< 0 (length rt-liber-rest-username))
+          (< 0 (length rt-liber-rest-password)))
+      t
+    (message "rt-liber: no REST credentials set, so attempting auth-source")
+    (let ((auth-source-found-p
+          (auth-source-search :host "rt-liberation" :require '(:user :secret) 
:create nil)))
+      (when (not auth-source-found-p)
+       (error "no auth-source found for login"))
+      (setq rt-liber-rest-password (funcall (plist-get (nth 0 
auth-source-found-p) :secret))
+           rt-liber-rest-username (plist-get (nth 0 auth-source-found-p) 
:user)))))
+
 (defun rt-liber-rest-search-string (scheme url username password query)
   "Return the search query string."
   (let ((user (url-encode-url username))
@@ -124,11 +146,11 @@
          str)
       (setq str
            (decode-coding-string
-           (with-current-buffer response
-             (buffer-substring-no-properties (point-min)
-                                             (point-max)))
-           'utf-8))
-      
+            (with-current-buffer response
+              (buffer-substring-no-properties (point-min)
+                                              (point-max)))
+            'utf-8))
+      (message "done")
       (rt-liber-rest-write-debug
        (format "outgoing rest call -->\n%s\n<-- incoming\n%s\n" url str))
       str)))
@@ -138,6 +160,7 @@
   (when (or (not (stringp op))
            (not (stringp query-string)))
     (error "bad arguments"))
+  (rt-liber-rest-auth)
   (cond ((string= op "ls")
         (rt-liber-rest-call
          (rt-liber-rest-search-string rt-liber-rest-scheme
@@ -215,6 +238,7 @@
   "Run edit comment to set FIELD to VALUE."
   (message "started edit command at %s..." (current-time-string))
   (message "ticket #%s, %s <- %s" ticket-id field value)
+  (rt-liber-rest-auth)
   (let ((request-data
         (format "content=%s: %s"
                 (url-hexify-string field)
diff --git a/rt-liberation-storage.el b/rt-liberation-storage.el
index df82ce1..f8e6884 100644
--- a/rt-liberation-storage.el
+++ b/rt-liberation-storage.el
@@ -1,4 +1,4 @@
-;;; rt-liberation-storage.el --- Storage backend for rt-liberation
+;;; rt-liberation-storage.el --- Storage backend for rt-liberation  -*- 
lexical-binding: t; -*-
 
 ;; Copyright (C) 2010  Free Software Foundation, Inc.
 ;;
diff --git a/rt-liberation-update.el b/rt-liberation-update.el
index bdd0f75..17ddba3 100644
--- a/rt-liberation-update.el
+++ b/rt-liberation-update.el
@@ -1,4 +1,4 @@
-;;; rt-liberation-update.el --- check updated tickets
+;;; rt-liberation-update.el --- check updated tickets  -*- lexical-binding: t; 
-*-
 
 ;; Copyright (C) 2009  Free Software Foundation, Inc.
 ;;
diff --git a/rt-liberation.el b/rt-liberation.el
index f30e048..4ee51c9 100644
--- a/rt-liberation.el
+++ b/rt-liberation.el
@@ -1,11 +1,11 @@
-;;; rt-liberation.el --- Emacs interface to RT
+;;; rt-liberation.el --- Emacs interface to RT  -*- lexical-binding: t; -*-
 
 ;; Copyright (C) 2008-2020 Free Software Foundation, Inc.
 
 ;; Author: Yoni Rabkin <yrk@gnu.org>
 ;; Authors: Aaron S. Hawley <aaron.s.hawley@gmail.com>, John Sullivan 
<johnsu01@wjsullivan.net>
 ;; Maintainer: Yoni Rabkin <yrk@gnu.org>
-;; Version: 1.31
+;; Version: 2.01
 ;; Keywords: rt, tickets
 ;; Package-Type: multi
 ;; url: http://www.nongnu.org/rtliber/
@@ -38,13 +38,16 @@
 
 
 ;;; Code:
-
 (require 'browse-url)
 (require 'time-date)
 (require 'cl-lib)
 
 (require 'rt-liberation-rest)
 
+(declare-function rt-liber-get-ancillary-text "rt-liberation-storage.el")
+(declare-function rt-liber-ticket-marked-p "rt-liberation-multi.el")
+(declare-function rt-liber-set-ancillary-text "rt-liberation-storage.el")
+
 
 (defgroup rt-liber nil
   "*rt-liberation, the Emacs interface to RT"
@@ -56,6 +59,24 @@
   :type 'string
   :group 'rt-liber)
 
+(defvar rt-liber-viewer-section-header-regexp
+  "^# [0-9]+/[0-9]+ (id/[0-9]+/total)")
+
+(defvar rt-liber-viewer-section-field-regexp
+  "^\\(.+\\): \\(.+\\)$")
+
+(defconst rt-liber-viewer-font-lock-keywords
+  (let ((header-regexp (regexp-opt '("id: " "Ticket: " "TimeTaken: "
+                                    "Type: " "Field: " "OldValue: "
+                                    "NewValue: " "Data: "
+                                    "Description: " "Created: "
+                                    "Creator: " "Attachments: ")
+                                  t)))
+    (list
+     (list (concat "^" header-regexp ".*$") 0
+          'font-lock-comment-face)))
+  "Expressions to font-lock for RT ticket viewer.")
+
 (defvar rt-liber-created-string "Created"
   "String representation of \"created\" query tag.")
 
@@ -112,7 +133,6 @@
 (defvar rt-liber-browser-default-filter-function
   'rt-liber-default-filter-f
   "Default filtering function.
-
 This is a function which accepts the ticket alist as a single
 argument and returns nil if the ticket needs to be filtered out,
 dropped or ignored (however you wish to put it.), otherwise the
@@ -163,17 +183,6 @@ function returns a truth value.")
     (t (:background "Black")))
   "Face for high priority tickets in browser buffer.")
 
-(defconst rt-liber-viewer-font-lock-keywords
-  (let ((header-regexp (regexp-opt '("id: " "Ticket: " "TimeTaken: "
-                                    "Type: " "Field: " "OldValue: "
-                                    "NewValue: " "Data: "
-                                    "Description: " "Created: "
-                                    "Creator: " "Attachments: ") t)))
-    (list
-     (list (concat "^" header-regexp ".*$") 0
-          'font-lock-comment-face)))
-  "Expressions to font-lock for RT ticket viewer.")
-
 (defvar rt-liber-browser-do-refresh t
   "When t, run `rt-liber-browser-refresh' otherwise disable it.")
 
@@ -193,7 +202,6 @@ server.")
     (status  . "Status")
     (priority  . "Priority"))
   "Mapping between field symbols and RT field strings.
-
 The field symbols provide the programmer with a consistent way of
 referring to RT fields.")
 
@@ -203,14 +211,12 @@ referring to RT fields.")
     (open     . "open")
     (new      . "new"))
   "Mapping between status symbols and status strings.
-
 The status symbols provide the programmer with a consistent way
 of referring to certain statuses. The status strings are the
 server specific strings.")
 
 (defvar rt-liber-debug-log-enable nil
   "If t then enable logging of communication to a buffer.
-
 Careful! This might create a sizable buffer.")
 
 (defvar rt-liber-debug-log-buffer-name "*rt-liber debug log*"
@@ -218,19 +224,35 @@ Careful! This might create a sizable buffer.")
 
 (defvar rt-liber-ticket-local nil
   "Buffer local storage for a ticket.
-
 This variable is made buffer local for the ticket history")
 
 (defvar rt-liber-assoc-browser nil
   "Browser associated with a ticket history.
-
 This variable is made buffer local for the ticket history")
 
+(defcustom rt-liber-gnus-comment-address "no comment address set"
+  "*Email address for adding a comment."
+  :type 'string
+  :group 'rt-liber-gnus)
+
+(defcustom rt-liber-gnus-address "no reply address set"
+  "*Email address for replying to requestor."
+  :type 'string
+  :group 'rt-liber-gnus)
+
+(defvar rt-liber-display-ticket-at-point-f 
'rt-liber-viewer2-display-ticket-at-point
+  "Function for displaying ticket at point in the browser.")
+
+(defvar rt-liber-viewer2-section-regexp "^Ticket [0-9]+ by "
+  "Regular expression to match a section in the viewer.")
+
+(defvar rt-liber-viewer2-recenter 4
+  "Argument passed to `recenter' in the viewer.")
+
 
 ;;; --------------------------------------------------------
 ;;; Debug log
 ;;; --------------------------------------------------------
-
 (defun rt-liber-debug-log-write (str)
   "Write STR to debug log."
   (when (not (stringp str))
@@ -244,7 +266,6 @@ This variable is made buffer local for the ticket history")
 ;;; --------------------------------------------------------
 ;;; TicketSQL compiler
 ;;; --------------------------------------------------------
-
 (defun rt-liber-bool-p (sym)
   "Return t if SYM is a boolean operator, otherwise nil."
   (member sym '(and or)))
@@ -352,7 +373,6 @@ AFTER  date after predicate."
 ;;; --------------------------------------------------------
 ;;; Parse Answer
 ;;; --------------------------------------------------------
-
 (defun rt-liber-parse-answer (answer-string parser-f)
   "Operate on ANSWER-STRING with PARSER-F."
   (with-temp-buffer
@@ -367,7 +387,6 @@ AFTER  date after predicate."
 ;;; --------------------------------------------------------
 ;;; Ticket list retriever
 ;;; --------------------------------------------------------
-
 (put 'rt-liber-no-result-from-query-error
      'error-conditions
      '(error rt-liber-errors rt-liber-no-result-from-query-error))
@@ -435,7 +454,6 @@ AFTER  date after predicate."
 ;;; --------------------------------------------------------
 ;;; Ticket utilities
 ;;; --------------------------------------------------------
-
 (defun rt-liber-ticket-days-old (ticket-alist)
   "Return the age of the ticket in positive days."
   (days-between (format-time-string "%Y-%m-%dT%T%z" (current-time))
@@ -445,26 +463,6 @@ AFTER  date after predicate."
   (<= rt-liber-ticket-old-threshold
       (rt-liber-ticket-days-old ticket-alist)))
 
-
-;;; --------------------------------------------------------
-;;; Ticket viewer
-;;; --------------------------------------------------------
-
-(defun rt-liber-jump-to-latest-correspondence ()
-  "Move point to the newest correspondence section."
-  (interactive)
-  (let (latest-point)
-    (save-excursion
-      (goto-char (point-max))
-      (when (re-search-backward rt-liber-correspondence-regexp
-                               (point-min) t)
-       (setq latest-point (point))))
-    (if latest-point
-       (progn
-         (goto-char latest-point)
-         (rt-liber-next-section-in-viewer))
-      (message "no correspondence found"))))
-
 (defun rt-liber-ticket-id-only (ticket-alist)
   "Return numerical portion of ticket number from TICKET-ALIST."
   (if ticket-alist
@@ -480,11 +478,6 @@ AFTER  date after predicate."
          nil))
     nil))
 
-(defun rt-liber-get-field-string (field-symbol)
-  (when (not field-symbol)
-    (error "null field symbol"))
-  (cdr (assoc field-symbol rt-liber-field-dictionary)))
-
 (defun rt-liber-ticket-owner-only (ticket-alist)
   "Return the string value of the ticket owner."
   (when (not ticket-alist)
@@ -492,284 +485,94 @@ AFTER  date after predicate."
   (cdr (assoc (rt-liber-get-field-string 'owner)
              ticket-alist)))
 
-(defun rt-liber-viewer-visit-in-browser ()
-  "Visit this ticket in the RT Web interface."
-  (interactive)
-  (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local)))
-    (if id
-       (browse-url
-        (concat rt-liber-base-url "Ticket/Display.html?id=" id))
-      (error "no ticket currently in view"))))
-
-(defun rt-liber-viewer-mode-quit ()
-  "Bury the ticket viewer."
-  (interactive)
-  (bury-buffer))
-
-(defun rt-liber-viewer-show-ticket-browser ()
-  "Return to the ticket browser buffer."
-  (interactive)
-  (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local)))
-    (if id
-       (let ((target-buffer
-              (if rt-liber-assoc-browser
-                  (buffer-name rt-liber-assoc-browser)
-                (buffer-name rt-liber-browser-buffer-name))))
-         (if target-buffer
-             (switch-to-buffer target-buffer)
-           (error "associated ticket browser buffer no longer exists"))
-         (rt-liber-browser-move-point-to-ticket id))
-      (error "no ticket currently in view"))))
+(defun rt-liber-get-field-string (field-symbol)
+  (when (not field-symbol)
+    (error "null field symbol"))
+  (cdr (assoc field-symbol rt-liber-field-dictionary)))
 
-(defun rt-liber-next-section-in-viewer ()
-  "Move point to next section."
-  (interactive)
-  (forward-line 1)
-  (when (not (re-search-forward rt-liber-content-regexp (point-max) t))
-    (message "no next section"))
-  (goto-char (point-at-bol)))
 
-(defun rt-liber-previous-section-in-viewer ()
-  "Move point to previous section."
-  (interactive)
-  (forward-line -1)
-  (when (not (re-search-backward rt-liber-content-regexp (point-min) t))
-    (message "no previous section"))
-  (goto-char (point-at-bol)))
+;;; --------------------------------------------------------
+;;; Ticket browser
+;;; --------------------------------------------------------
+;; accept a ticket-alist object and return an alist mapping ticket
+;; properties to format characters for use in `rt-liber-format'.
+(defun rt-liber-format-function (ticket-alist)
+  "Return a pairing of TICKET-ALIST values to %-sequences."
+  (let* ((id         (rt-liber-ticket-id-only ticket-alist))
+        (subject    (cdr (assoc "Subject" ticket-alist)))
+        (status     (cdr (assoc "Status" ticket-alist)))
+        (created    (format-time-string
+                     rt-liber-browser-time-format-string
+                     (date-to-time
+                      (cdr (assoc "Created" ticket-alist)))))
+        (resolved   (cdr (assoc "Resolved" ticket-alist)))
+        (requestors (cdr (assoc "Requestors" ticket-alist)))
+        (creator    (cdr (assoc "Creator" ticket-alist)))
+        (owner      (rt-liber-ticket-owner-only ticket-alist))
+        (queue      (cdr (assoc "Queue" ticket-alist)))
+        (anc        (if rt-liber-anc-p
+                        (rt-liber-get-ancillary-text
+                         (rt-liber-ticket-id-only ticket-alist))
+                      ""))
+        (priority   (cdr (assoc "Priority" ticket-alist))))
+    (list (cons ?i (or id "N/A"))
+         (cons ?s (or subject "N/A"))
+         (cons ?c (or created "N/A"))
+         (cons ?S (or status "N/A"))
+         (cons ?r (or resolved "N/A"))
+         (cons ?R (or requestors "N/A"))
+         (cons ?C (or creator "N/A"))
+         (cons ?o (or owner "N/A"))
+         (cons ?q (or queue "N/A"))
+         (cons ?A (or anc ""))
+         (cons ?p (or priority "N/A")))))
 
-(defconst rt-liber-viewer-mode-map
-  (let ((map (make-sparse-keymap)))
-    (define-key map (kbd "q") 'rt-liber-viewer-mode-quit)
-    (define-key map (kbd "n") 'rt-liber-next-section-in-viewer)
-    (define-key map (kbd "N") 'rt-liber-jump-to-latest-correspondence)
-    (define-key map (kbd "p") 'rt-liber-previous-section-in-viewer)
-    (define-key map (kbd "V") 'rt-liber-viewer-visit-in-browser)
-    (define-key map (kbd "m") 'rt-liber-viewer-answer)
-    (define-key map (kbd "M") 'rt-liber-viewer-answer-this)
-    (define-key map (kbd "t") 'rt-liber-viewer-answer-provisionally)
-    (define-key map (kbd "T") 'rt-liber-viewer-answer-provisionally-this)
-    (define-key map (kbd "F") 'rt-liber-viewer-answer-verbatim-this)
-    (define-key map (kbd "c") 'rt-liber-viewer-comment)
-    (define-key map (kbd "C") 'rt-liber-viewer-comment-this)
-    (define-key map (kbd "g") 'revert-buffer)
-    (define-key map (kbd "SPC") 'scroll-up)
-    (define-key map (kbd "DEL") 'scroll-down)
-    (define-key map (kbd "h") 'rt-liber-viewer-show-ticket-browser)
-    map)
-  "Key map for ticket viewer.")
+(defun rt-liber-browser-assoc (char alist)
+  "Process the %-sequence association."
+  (let ((v (cdr (assoc char alist))))
+    (cond ((eq char ?%) "%") ;; escape sequence for %
+         (t (or v "")))))
 
-(define-derived-mode rt-liber-viewer-mode nil
-  "RT Liberation Viewer"
-  "Major Mode for viewing RT tickets.
-\\{rt-liber-viewer-mode-map}"
-  (set
-   (make-local-variable 'font-lock-defaults)
-   '((rt-liber-viewer-font-lock-keywords)))
-  (set (make-local-variable 'revert-buffer-function)
-       #'rt-liber-refresh-ticket-history)
-  (set (make-local-variable 'buffer-stale-function)
-       (lambda (&optional _noconfirm) 'slow))
-  (when rt-liber-jump-to-latest
-    (rt-liber-jump-to-latest-correspondence))
-  (run-hooks 'rt-liber-viewer-hook))
+(defun rt-liber-high-priority-p (ticket-alist)
+  "Return t if TICKET-ALIST is high priority.
 
-(defun rt-liber-display-ticket-history (ticket-alist &optional assoc-browser)
-  "Display history for ticket.
+The ticket's priority is compared to the variable
+  `rt-liber-browser-priority-cutoff'."
+  (let ((p (rt-liber-ticket-priority-only ticket-alist)))
+    (if p
+       (< rt-liber-browser-priority-cutoff p)
+      nil)))
 
-TICKET-ALIST alist of ticket data.
-ASSOC-BROWSER if non-nil should be a ticket browser."
-  (let* ((ticket-id (rt-liber-ticket-id-only ticket-alist))
-        (contents (rt-liber-rest-run-ticket-history-base-query ticket-id))
-        (new-ticket-buffer (get-buffer-create
-                            (concat "*RT Ticket #" ticket-id "*"))))
-    (with-current-buffer new-ticket-buffer
-      (let ((inhibit-read-only t))
-       (erase-buffer)
-       (insert contents)
-       (goto-char (point-min))
-       (rt-liber-viewer-mode)
-       (set
-        (make-local-variable 'rt-liber-ticket-local)
-        ticket-alist)
-       (when assoc-browser
-         (set
-          (make-local-variable 'rt-liber-assoc-browser)
-          assoc-browser))
-       (set-buffer-modified-p nil)
-       (setq buffer-read-only t)))
-    (switch-to-buffer new-ticket-buffer)))
+(defun rt-liber-format (format ticket-alist)
+  "Substitute %-sequences in FORMAT."
+  (let ((alist (rt-liber-format-function ticket-alist)))
+    (replace-regexp-in-string
+     "%."
+     (lambda (str)
+       (rt-liber-browser-assoc (aref str 1) alist))
+     format t t)))
 
-(defun rt-liber-refresh-ticket-history (&optional _ignore-auto _noconfirm)
-  (interactive)
-  (if rt-liber-ticket-local
-      (rt-liber-display-ticket-history rt-liber-ticket-local
-                                      rt-liber-assoc-browser)
-    (error "not viewing a ticket")))
+(defun rt-liber-ticketlist-browser-redraw-f (ticket)
+  "Display TICKET."
+  (insert (rt-liber-format "[%c %i %S]" ticket))
+  (add-text-properties (point-at-bol)
+                      (point-at-eol)
+                      '(face rt-liber-ticket-face))
+  (when (rt-liber-high-priority-p ticket)
+    (let ((p (point)))
+      (insert (format " HIGH PRIORITY (%d)"
+                     (rt-liber-ticket-priority-only ticket)))
+      (add-text-properties p
+                          (point-at-eol)
+                          '(face rt-liber-priority-ticket-face))))
 
-;; wrapper functions around specific functions provided by a backend
-
-(declare-function
- rt-liber-gnus-compose-reply-to-requestor
- "rt-liberation-gnus.el")
-(declare-function
- rt-liber-gnus-compose-reply-to-requestor-to-this
- "rt-liberation-gnus.el")
-(declare-function
- rt-liber-gnus-compose-reply-to-requestor-verbatim-this
- "rt-liberation-gnus.el")
-(declare-function
- rt-liber-gnus-compose-provisional
- "rt-liberation-gnus.el")
-(declare-function
- rt-liber-gnus-compose-provisional-to-this
- "rt-liberation-gnus.el")
-(declare-function
- rt-liber-gnus-compose-comment
- "rt-liberation-gnus.el")
-(declare-function
- rt-liber-gnus-compose-comment-this
- "rt-liberation-gnus.el")
-
-(defun rt-liber-viewer-answer ()
-  "Answer the ticket."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-reply-to-requestor))
-       (t (error "no function defined"))))
-
-(defun rt-liber-viewer-answer-this ()
-  "Answer the ticket using the current context."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-reply-to-requestor-to-this))
-       (t (error "no function defined"))))
-
-(defun rt-liber-viewer-answer-verbatim-this ()
-  "Answer the ticket using the current context verbatim."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-reply-to-requestor-verbatim-this))
-       (t (error "no function defined"))))
-
-(defun rt-liber-viewer-answer-provisionally ()
-  "Provisionally answer the ticket."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-provisional))
-       (t (error "no function defined"))))
-
-(defun rt-liber-viewer-answer-provisionally-this ()
-  "Provisionally answer the ticket using the current context."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-provisional-to-this))
-       (t (error "no function defined"))))
-
-(defun rt-liber-viewer-comment ()
-  "Comment on the ticket."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-comment))
-       (t (error "no function defined"))))
-
-(defun rt-liber-viewer-comment-this ()
-  "Comment on the ticket using the current context."
-  (interactive)
-  (cond ((featurep 'rt-liberation-gnus)
-        (rt-liber-gnus-compose-comment-this))
-       (t (error "no function defined"))))
-
-
-;;; --------------------------------------------------------
-;;; Ticket browser
-;;; --------------------------------------------------------
-
-(declare-function
- rt-liber-get-ancillary-text
- "rt-liberation-storage.el")
-
-;; accept a ticket-alist object and return an alist mapping ticket
-;; properties to format characters for use in `rt-liber-format'.
-(defun rt-liber-format-function (ticket-alist)
-  "Return a pairing of TICKET-ALIST values to %-sequences."
-  (let* ((id         (rt-liber-ticket-id-only ticket-alist))
-        (subject    (cdr (assoc "Subject" ticket-alist)))
-        (status     (cdr (assoc "Status" ticket-alist)))
-        (created    (format-time-string
-                     rt-liber-browser-time-format-string
-                     (date-to-time
-                      (cdr (assoc "Created" ticket-alist)))))
-        (resolved   (cdr (assoc "Resolved" ticket-alist)))
-        (requestors (cdr (assoc "Requestors" ticket-alist)))
-        (creator    (cdr (assoc "Creator" ticket-alist)))
-        (owner      (rt-liber-ticket-owner-only ticket-alist))
-        (queue      (cdr (assoc "Queue" ticket-alist)))
-        (anc        (if rt-liber-anc-p
-                        (rt-liber-get-ancillary-text
-                         (rt-liber-ticket-id-only ticket-alist))
-                      ""))
-        (priority   (cdr (assoc "Priority" ticket-alist))))
-    (list (cons ?i (or id "N/A"))
-         (cons ?s (or subject "N/A"))
-         (cons ?c (or created "N/A"))
-         (cons ?S (or status "N/A"))
-         (cons ?r (or resolved "N/A"))
-         (cons ?R (or requestors "N/A"))
-         (cons ?C (or creator "N/A"))
-         (cons ?o (or owner "N/A"))
-         (cons ?q (or queue "N/A"))
-         (cons ?A (or anc ""))
-         (cons ?p (or priority "N/A")))))
-
-(defun rt-liber-browser-assoc (char alist)
-  "Process the %-sequence association."
-  (let ((v (cdr (assoc char alist))))
-    (cond ((eq char ?%) "%") ;; escape sequence for %
-         (t (or v "")))))
-
-(defun rt-liber-high-priority-p (ticket-alist)
-  "Return t if TICKET-ALIST is high priority.
-
-The ticket's priority is compared to the variable
-  `rt-liber-browser-priority-cutoff'."
-  (let ((p (rt-liber-ticket-priority-only ticket-alist)))
-    (if p
-       (< rt-liber-browser-priority-cutoff p)
-      nil)))
-
-(defun rt-liber-format (format ticket-alist)
-  "Substitute %-sequences in FORMAT."
-  (let ((alist (rt-liber-format-function ticket-alist)))
-    (replace-regexp-in-string
-     "%."
-     (lambda (str)
-       (rt-liber-browser-assoc (aref str 1) alist))
-     format t t)))
-
-(defun rt-liber-ticketlist-browser-redraw-f (ticket)
-  "Display TICKET."
-  (insert (rt-liber-format "[%c %i %S]" ticket))
-  (add-text-properties (point-at-bol)
-                      (point-at-eol)
-                      '(face rt-liber-ticket-face))
-  (when (rt-liber-high-priority-p ticket)
-    (let ((p (point)))
-      (insert (format " HIGH PRIORITY (%d)"
-                     (rt-liber-ticket-priority-only ticket)))
-      (add-text-properties p
-                          (point-at-eol)
-                          '(face rt-liber-priority-ticket-face))))
-
-  (newline)
-  (insert (rt-liber-format "  [%o] %R: %s" ticket))
-  (let ((p (point)))
-    (insert (rt-liber-format "    %A" ticket))
-    (add-text-properties p (point)
-                        '(face font-lock-comment-face)))
-  (newline))
-
-(declare-function rt-liber-ticket-marked-p
-                 "rt-liberation-multi.el")
+  (newline)
+  (insert (rt-liber-format "  [%o] %R: %s" ticket))
+  (let ((p (point)))
+    (insert (rt-liber-format "    %A" ticket))
+    (add-text-properties p (point)
+                        '(face font-lock-comment-face)))
+  (newline))
 
 (defun rt-liber-ticketlist-browser-redraw (ticketlist &optional query)
   "Display TICKETLIST. Optionally display QUERY as well."
@@ -861,6 +664,11 @@ If POINT is nil then called on (point)."
   (let ((ticket-alist (get-text-property (point) 'rt-ticket)))
     (rt-liber-display-ticket-history ticket-alist (current-buffer))))
 
+(defun rt-liber-ticket-at-point ()
+  "Display the contents of the ticket at point."
+  (interactive)
+  (funcall rt-liber-display-ticket-at-point-f))
+
 (defun rt-liber-browser-search (id)
   "Return point where ticket with ID is displayed or nil."
   (let ((p nil))
@@ -895,7 +703,6 @@ If POINT is nil then called on (point)."
 ;;; --------------------------------------------------------
 ;;; Ticket browser sorting
 ;;; --------------------------------------------------------
-
 (defun rt-liber-lex-lessthan-p (a b field)
   "Return t if A is lexicographically less than B in FIELD."
   (let ((field-a (cdr (assoc field a)))
@@ -938,7 +745,6 @@ If POINT is nil then called on (point)."
 ;;; --------------------------------------------------------
 ;;; Ticket browser filtering
 ;;; --------------------------------------------------------
-
 ;; See the fine manual for example code.
 
 (defun rt-liber-default-filter-f (_ticket)
@@ -952,7 +758,6 @@ and as such always return t."
 ;;; --------------------------------------------------------
 ;;; Entry points
 ;;; --------------------------------------------------------
-
 (defun rt-liber-browse-query (query &optional new)
   "Run QUERY against the server and launch the browser.
 
@@ -995,7 +800,6 @@ returned as no associated text properties."
 ;;; --------------------------------------------------------
 ;;; Major mode definitions
 ;;; --------------------------------------------------------
-
 (defun rt-liber-browser-mode-quit ()
   "Bury the ticket browser."
   (interactive)
@@ -1006,7 +810,7 @@ returned as no associated text properties."
     (define-key map (kbd "q") 'rt-liber-browser-mode-quit)
     (define-key map (kbd "n") 'rt-liber-next-ticket-in-browser)
     (define-key map (kbd "p") 'rt-liber-previous-ticket-in-browser)
-    (define-key map (kbd "RET") 'rt-liber-display-ticket-at-point)
+    (define-key map (kbd "RET") 'rt-liber-ticket-at-point)
     (define-key map (kbd "g") 'revert-buffer)
     (define-key map (kbd "G") 'rt-liber-browser-refresh-and-return)
     (define-key map (kbd "a") 'rt-liber-browser-assign)
@@ -1081,8 +885,6 @@ returned as no associated text properties."
   (switch-to-buffer rt-liber-browser-buffer)
   (setq buffer-read-only t))
 
-(declare-function rt-liber-set-ancillary-text "rt-liberation-storage.el")
-
 (defun rt-liber-browser-ancillary-text ()
   "Wrapper function around storage backend."
   (interactive)
@@ -1096,7 +898,6 @@ returned as no associated text properties."
 ;;; --------------------------------------------------------
 ;;; Command module
 ;;; --------------------------------------------------------
-
 (defun rt-liber-command-get-dictionary-value (sym dic)
   "Utility function for retrieving alist values."
   (let ((value (cdr (assoc sym dic))))
@@ -1210,6 +1011,608 @@ returned as no associated text properties."
   (rt-liber-browser-assign rt-liber-username))
 
 
+;;; --------------------------------------------------------
+;;; Viewer
+;;; --------------------------------------------------------
+(defun rt-liber-display-ticket-history (ticket-alist &optional assoc-browser)
+  "Display history for ticket.
+TICKET-ALIST alist of ticket data.
+ASSOC-BROWSER if non-nil should be a ticket browser."
+  (let* ((ticket-id (rt-liber-ticket-id-only ticket-alist))
+        (contents (rt-liber-rest-run-ticket-history-base-query ticket-id))
+        (new-ticket-buffer (get-buffer-create
+                            (concat "*RT Ticket #" ticket-id "*"))))
+    (with-current-buffer new-ticket-buffer
+      (let ((inhibit-read-only t))
+       (erase-buffer)
+       (insert contents)
+       (goto-char (point-min))
+       (rt-liber-viewer-mode)
+       (set
+        (make-local-variable 'rt-liber-ticket-local)
+        ticket-alist)
+       (when assoc-browser
+         (set
+          (make-local-variable 'rt-liber-assoc-browser)
+          assoc-browser))
+       (set-buffer-modified-p nil)
+       (setq buffer-read-only t)))
+    (switch-to-buffer new-ticket-buffer)))
+
+
+;;; ------------------------------------------------------------------
+;;; viewer mode functions
+;;; ------------------------------------------------------------------
+(defun rt-liber-refresh-ticket-history (&optional _ignore-auto _noconfirm)
+  (interactive)
+  (if rt-liber-ticket-local
+      (rt-liber-display-ticket-history rt-liber-ticket-local
+                                       rt-liber-assoc-browser)
+    (error "not viewing a ticket")))
+
+(defun rt-liber-jump-to-latest-correspondence ()
+  "Move point to the newest correspondence section."
+  (interactive)
+  (let (latest-point)
+    (save-excursion
+      (goto-char (point-max))
+      (when (re-search-backward rt-liber-correspondence-regexp
+                               (point-min) t)
+       (setq latest-point (point))))
+    (if latest-point
+       (progn
+         (goto-char latest-point)
+         (rt-liber-next-section-in-viewer))
+      (message "no correspondence found"))))
+
+(defun rt-liber-viewer-visit-in-browser ()
+  "Visit this ticket in the RT Web interface."
+  (interactive)
+  (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local)))
+    (if id
+       (browse-url
+        (concat rt-liber-base-url "Ticket/Display.html?id=" id))
+      (error "no ticket currently in view"))))
+
+(defun rt-liber-viewer-mode-quit ()
+  "Bury the ticket viewer."
+  (interactive)
+  (bury-buffer))
+
+(defun rt-liber-viewer-show-ticket-browser ()
+  "Return to the ticket browser buffer."
+  (interactive)
+  (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local)))
+    (if id
+       (let ((target-buffer
+              (if rt-liber-assoc-browser
+                  (buffer-name rt-liber-assoc-browser)
+                (buffer-name rt-liber-browser-buffer-name))))
+         (if target-buffer
+             (switch-to-buffer target-buffer)
+           (error "associated ticket browser buffer no longer exists"))
+         (rt-liber-browser-move-point-to-ticket id))
+      (error "no ticket currently in view"))))
+
+(defun rt-liber-next-section-in-viewer ()
+  "Move point to next section."
+  (interactive)
+  (forward-line 1)
+  (when (not (re-search-forward rt-liber-content-regexp (point-max) t))
+    (message "no next section"))
+  (goto-char (point-at-bol)))
+
+(defun rt-liber-previous-section-in-viewer ()
+  "Move point to previous section."
+  (interactive)
+  (forward-line -1)
+  (when (not (re-search-backward rt-liber-content-regexp (point-min) t))
+    (message "no previous section"))
+  (goto-char (point-at-bol)))
+
+(defconst rt-liber-viewer-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "q") 'rt-liber-viewer-mode-quit)
+    (define-key map (kbd "n") 'rt-liber-next-section-in-viewer)
+    (define-key map (kbd "N") 'rt-liber-jump-to-latest-correspondence)
+    (define-key map (kbd "p") 'rt-liber-previous-section-in-viewer)
+    (define-key map (kbd "V") 'rt-liber-viewer-visit-in-browser)
+    (define-key map (kbd "m") 'rt-liber-viewer-answer)
+    (define-key map (kbd "M") 'rt-liber-viewer-answer-this)
+    (define-key map (kbd "t") 'rt-liber-viewer-answer-provisionally)
+    (define-key map (kbd "T") 'rt-liber-viewer-answer-provisionally-this)
+    (define-key map (kbd "F") 'rt-liber-viewer-answer-verbatim-this)
+    (define-key map (kbd "c") 'rt-liber-viewer-comment)
+    (define-key map (kbd "C") 'rt-liber-viewer-comment-this)
+    (define-key map (kbd "g") 'revert-buffer)
+    (define-key map (kbd "SPC") 'scroll-up)
+    (define-key map (kbd "DEL") 'scroll-down)
+    (define-key map (kbd "h") 'rt-liber-viewer-show-ticket-browser)
+    map)
+  "Key map for ticket viewer.")
+
+(define-derived-mode rt-liber-viewer-mode nil
+  "RT Liberation Viewer"
+  "Major Mode for viewing RT tickets.
+\\{rt-liber-viewer-mode-map}"
+  (set
+   (make-local-variable 'font-lock-defaults)
+   '((rt-liber-viewer-font-lock-keywords)))
+  (set (make-local-variable 'revert-buffer-function)
+       #'rt-liber-refresh-ticket-history)
+  (set (make-local-variable 'buffer-stale-function)
+       (lambda (&optional _noconfirm) 'slow))
+  (when rt-liber-jump-to-latest
+    (rt-liber-jump-to-latest-correspondence))
+  (run-hooks 'rt-liber-viewer-hook))
+
+;; wrapper functions around specific functions provided by a backend
+(declare-function
+ rt-liber-gnus-compose
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-reply-to-requestor
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-reply-to-requestor-to-this
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-reply-to-requestor-verbatim-this
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-provisional
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-provisional-to-this
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-comment
+ "rt-liberation-gnus.el")
+(declare-function
+ rt-liber-gnus-compose-comment-this
+ "rt-liberation-gnus.el")
+
+(defun rt-liber-viewer-answer ()
+  "Answer the ticket."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-reply-to-requestor))
+       (t (error "no function defined"))))
+
+(defun rt-liber-viewer-answer-this ()
+  "Answer the ticket using the current context."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-reply-to-requestor-to-this))
+       (t (error "no function defined"))))
+
+(defun rt-liber-viewer-answer-verbatim-this ()
+  "Answer the ticket using the current context verbatim."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-reply-to-requestor-verbatim-this))
+       (t (error "no function defined"))))
+
+(defun rt-liber-viewer-answer-provisionally ()
+  "Provisionally answer the ticket."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-provisional))
+       (t (error "no function defined"))))
+
+(defun rt-liber-viewer-answer-provisionally-this ()
+  "Provisionally answer the ticket using the current context."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-provisional-to-this))
+       (t (error "no function defined"))))
+
+(defun rt-liber-viewer-comment ()
+  "Comment on the ticket."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-comment))
+       (t (error "no function defined"))))
+
+(defun rt-liber-viewer-comment-this ()
+  "Comment on the ticket using the current context."
+  (interactive)
+  (cond ((featurep 'rt-liberation-gnus)
+        (rt-liber-gnus-compose-comment-this))
+       (t (error "no function defined"))))
+
+
+;;; ------------------------------------------------------------------
+;;; viewer2
+;;; ------------------------------------------------------------------
+
+;; Comment: The goal is to eventually break this code away to its own
+;; file.
+
+(defface rt-liber-ticket-emph-face
+  '((((class color) (background dark))
+     (:foreground "gray53"))
+    (((class color) (background light))
+     (:foreground "gray65"))
+    (((type tty) (class mono))
+     (:inverse-video t))
+    (t (:background "Blue")))
+  "Face for important text.")
+
+(defconst rt-liber-viewer2-font-lock-keywords
+  `(("^$" 0 'rt-liber-ticket-subdued-face))
+  "Expressions to font-lock for RT ticket viewer.")
+
+
+(defun rt-liber-viewer2-vernacular-plural (time)
+  "Add an ess as needed."
+  (if (= time 1)
+      ""
+    "s"))
+
+(defun rt-liber-viewer2-vernacular-date (date)
+  "Return a vernacular time delta."
+  (let* ((now (format-time-string "%Y-%m-%dT%T%z" (current-time)))
+        (days-ago (days-between now date)))
+    (cond ((= 0 days-ago)
+          "today")
+         ((< 0 days-ago 7)
+          (format "%s day%s ago" days-ago
+                  (rt-liber-viewer2-vernacular-plural days-ago)))
+         ((< 7 days-ago 30)
+          (let ((weeks (floor (/ days-ago 7.0))))
+            (format "%s week%s ago"
+                    weeks
+                    (rt-liber-viewer2-vernacular-plural weeks))))
+         ((< 30 days-ago 365)
+          (let ((months (floor (/ days-ago 30.0))))
+            (format "%s month%s ago"
+                    months
+                    (rt-liber-viewer2-vernacular-plural months))))
+         (t (let ((years (floor (/ days-ago 365.0))))
+              (format "%s year%s ago"
+                      years
+                      (rt-liber-viewer2-vernacular-plural years)))))))
+
+(defun rt-liber-viewer2-mode-quit ()
+  "Bury the ticket viewer."
+  (interactive)
+  (bury-buffer))
+
+(defun rt-liber-viewer-reduce (section-list f acc)
+  "A Not Invented Here tail-recursive reduce function."
+  (cond ((null (cdr section-list)) acc)
+       (t (rt-liber-viewer-reduce (cdr section-list)
+                                  f
+                                  (append acc (list
+                                               (funcall f
+                                                        (car section-list)
+                                                        (cadr 
section-list))))))))
+
+;; According to:
+;; "https://rt-wiki.bestpractical.com/wiki/REST#Ticket_History_Entry";
+;; id: <history-id>
+;; Ticket: <ticket-id>
+;; TimeTaken: <...>
+;; Type: <...>
+;; Field: <...>
+;; OldValue: <...>
+;; NewValue: <...>
+;; Data: <...>
+;; Description: <...>
+
+;; Content: <lin1-0>
+;;          <line-1>
+;;          ...
+;;          <line-n>
+
+;; Creator: <...>
+;; Created: <...>
+;; Attachments: <...>
+(defun rt-liber-viewer-parse-section (start end)
+  (goto-char start)
+  (when (not (re-search-forward
+             rt-liber-viewer-section-header-regexp
+             end t))
+    (error "invalid section"))
+  (forward-line 2)
+  (let (section-field-alist
+       (rt-field-list
+        '(id Ticket TimeTaken Type Field
+             OldValue NewValue Data Description
+             Creator Created)))
+    ;; definitely error out if any of this doesn't work
+    (setq section-field-alist
+         (mapcar
+          (lambda (field-symbol)
+            (re-search-forward (format "^%s:" (symbol-name field-symbol)) end 
nil)
+            (cons field-symbol (buffer-substring (1+ (point)) (point-at-eol))))
+          rt-field-list))
+    ;; content
+    (goto-char start)
+    (let ((content-start (re-search-forward "^Content: " end nil))
+         (content-end (progn
+                        (re-search-forward "^Creator: " end nil)
+                        (point-at-bol))))
+      (append section-field-alist
+             `(,(cons 'Content
+                      (buffer-substring content-start
+                                        content-end)))))))
+
+;; According to:
+;; "https://rt-wiki.bestpractical.com/wiki/REST#Ticket_History"; is of
+;; the form: "# <n>/<n> (id/<history-id>/total)"
+(defun rt-liber-viewer-parse-history (ticket-history)
+  "Parse the string TICKET-HISTORY."
+  (when (not (stringp ticket-history))
+    (error "invalid ticket-history"))
+  (with-temp-buffer
+    (insert ticket-history)
+    (goto-char (point-min))
+    ;; find history detail sections and procude a list of section
+    ;; (start . end) pairs
+    (let (section-point-list
+         section-list)
+      (while (re-search-forward rt-liber-viewer-section-header-regexp 
(point-max) t)
+       (setq section-point-list (append section-point-list
+                                        (list (point-at-bol)))))
+      (when (not section-point-list)
+       (error "no history detail sections found"))
+      (setq section-point-list (append section-point-list
+                                      (list (point-max)))
+           section-point-list (rt-liber-viewer-reduce section-point-list 
#'cons nil))
+      ;; collect the sections
+      (setq section-list
+           (mapcar
+            (lambda (section-points)
+              (rt-liber-viewer-parse-section
+               (car section-points)
+               (cdr section-points)))
+            section-point-list))
+      section-list)))
+
+(defun rt-liber-viewer2-get-section-data ()
+  "Return the current section data."
+  (let ((section (get-text-property (point) 'rt-liberation-section-data)))
+    (when (not section)
+      (save-excursion
+       (rt-liber-viewer2-previous-section-in)
+       (setq section (get-text-property (point) 'rt-liberation-section-data))))
+    section))
+
+(defun rt-liber-viewer2-format-content (content)
+  "Wrangle RT's content format."
+  (with-temp-buffer
+    (insert content)
+    (goto-char (point-min))
+    (if (re-search-forward "^This transaction appears to have no content" 
(point-max) t)
+       "" ; make no content mean... no content
+      ;; trim leading blank lines
+      (save-excursion
+       (goto-char (point-min))
+       (re-search-forward "[[:graph:]]" (point-max) t)
+       (forward-line -1)
+       (flush-lines "^[[:space:]]+$" (point-min) (point)))
+      ;; trim trailing blank lines
+      (save-excursion
+       (goto-char (point-max))
+       (re-search-backward "[[:graph:]]" (point-min) t)
+       (forward-line 2)
+       (flush-lines "^[[:space:]]+$" (point-max) (point)))
+      ;; Convert the 9 leading whitespaces from RT's comment lines.
+      (goto-char (point-min))
+      (insert "    ")
+      (while (re-search-forward "^         " (point-max) t)
+       (replace-match "    "))
+      ;; fill
+      (let ((paragraph-separate "    >[[:space:]]+$"))
+       (fill-region (point-min)
+                    (point-max)))
+      ;; finally
+      (buffer-substring (point-min)
+                       (point-max)))))
+
+(defun rt-liber-viewer2-clean-content (section)
+  "Format section content for email."
+  (with-temp-buffer
+    (insert (rt-liber-viewer2-format-content
+            (alist-get 'Content section)))
+    (goto-char (point-min))
+    (while (re-search-forward "^    " (point-max) t)
+      (replace-match ""))
+    ;; fill
+    (let ((paragraph-separate ">[[:space:]]+$"))
+      (fill-region (point-min)
+                  (point-max)))
+    ;; finally
+    (buffer-substring (point-min)
+                     (point-max))))
+
+(defun rt-liber-viewer2-display-section (section)
+  (let ((start     (point))
+       (ticket-id (alist-get 'Ticket section))
+       (creator   (alist-get 'Creator section))
+       (date      (alist-get 'Created section))
+       (type      (alist-get 'Type section))
+       (oldvalue  (alist-get 'OldValue section))
+       (newvalue  (alist-get 'NewValue section))
+       (field     (alist-get 'Field section))
+       (s-content (rt-liber-viewer2-format-content
+                   (alist-get 'Content section))))
+    (when (not (or (string= type "CommentEmailRecord")
+                  (string= type "EmailRecord")))
+      (insert
+       (format "Ticket %s by %s, %s (%s) [%s]\n"
+              ticket-id
+              creator
+              (rt-liber-viewer2-vernacular-date date)
+              date
+              (format "%s%s%s"
+                      type
+                      (if (< 0 (length oldvalue))
+                          (concat " " oldvalue)
+                        "")
+                      (if (< 0 (length newvalue))
+                          (concat "->" newvalue)
+                        ""))))
+      (add-text-properties start
+                          (point)
+                           `(font-lock-face rt-liber-ticket-emph-face))
+      (add-text-properties start
+                          (point)
+                           `(rt-liberation-viewer-header t))
+      (add-text-properties start
+                          (point)
+                          `(rt-liberation-section-data ,section))
+      (cond ((string= type "Status")
+            (insert
+             (format "\n    Status change from %s to %s\n\n" oldvalue 
newvalue)))
+           ((and (string= type "Set")
+                 (string= field "Owner")
+                 (string= oldvalue "10"))
+            (insert
+             (format "\n    Ticket assigned\n\n")))
+           ;; catch-all
+           (t
+            (insert
+             (format "\n%s\n" s-content)))))))
+
+(defun rt-liber-viewer2-display-history (contents)
+  (let ((section-list (rt-liber-viewer-parse-history contents)))
+    (mapc
+     (lambda (section)
+       (rt-liber-viewer2-display-section section))
+     section-list)))
+
+(defun rt-liber-viewer2-display-ticket-at-point ()
+  "Display the contents of the ticket at point."
+  (interactive)
+  (let ((ticket-alist (get-text-property (point) 'rt-ticket)))
+    (rt-liber-viewer2-display-ticket-history ticket-alist (current-buffer))))
+
+(defun rt-liber-viewer2-display-ticket-history (ticket-alist &optional 
assoc-browser)
+  "Display history for ticket.
+TICKET-ALIST alist of ticket data.
+ASSOC-BROWSER if non-nil should be a ticket browser."
+  (let* ((ticket-id (rt-liber-ticket-id-only ticket-alist))
+        (contents (rt-liber-rest-run-ticket-history-base-query ticket-id))
+        (new-ticket-buffer (get-buffer-create
+                            (concat "*RT (Viewer) Ticket #" ticket-id "*"))))
+    (with-current-buffer new-ticket-buffer
+      (let ((inhibit-read-only t))
+       (erase-buffer)
+       (rt-liber-viewer2-display-history contents)
+       (goto-char (point-min))
+       (rt-liber-viewer2-mode)
+       (set
+        (make-local-variable 'rt-liber-ticket-local)
+        ticket-alist)
+       (when assoc-browser
+         (set
+          (make-local-variable 'rt-liber-assoc-browser)
+          assoc-browser))
+       (set-buffer-modified-p nil)
+       (setq buffer-read-only t)))
+    (switch-to-buffer new-ticket-buffer)))
+
+(defun rt-liber-viewer2-refresh-ticket-history (&optional _ignore-auto 
_noconfirm)
+  (interactive)
+  (if rt-liber-ticket-local
+      (rt-liber-viewer2-display-ticket-history rt-liber-ticket-local
+                                              rt-liber-assoc-browser)
+    (error "not viewing a ticket")))
+
+(defun rt-liber-viewer2-next-section-in ()
+  (interactive)
+  (when (looking-at rt-liber-viewer2-section-regexp)
+    (goto-char (1+ (point))))
+  (let ((next (re-search-forward rt-liber-viewer2-section-regexp
+                                (point-max)
+                                t)))
+    (cond ((not next)
+          (message "no next section"))
+         (t
+          (recenter rt-liber-viewer2-recenter)))
+    (goto-char (point-at-bol))))
+
+(defun rt-liber-viewer2-last-section-in ()
+  (interactive)
+  (goto-char (point-max))
+  (let ((last (re-search-backward rt-liber-viewer2-section-regexp
+                                 (point-min)
+                                 t)))
+    (if (not last)
+       (error "no sections found")
+      (recenter rt-liber-viewer2-recenter)
+      (goto-char (point-at-bol)))))
+
+(defun rt-liber-viewer2-previous-section-in ()
+  (interactive)
+  (when (looking-at rt-liber-viewer2-section-regexp)
+    (goto-char (1- (point))))
+  (let ((prev (re-search-backward rt-liber-viewer2-section-regexp
+                                 (point-min)
+                                 t)))
+    (cond ((not prev)
+          (message "no previous section"))
+         (t
+          (recenter rt-liber-viewer2-recenter)))
+    (goto-char (point-at-bol))))
+
+(defun rt-liber-viewer2-answer ()
+  (interactive)
+  (let ((section (rt-liber-viewer2-get-section-data)))
+    (when (not section)
+      (error "no section found"))
+    (if (not (featurep 'rt-liberation-gnus))
+       (error "rt-liberation-gnus feature not found")
+      (rt-liber-gnus-compose
+       rt-liber-gnus-address
+       rt-liber-ticket-local
+       `((contents . ,(rt-liber-viewer2-clean-content section)))))))
+
+(defun rt-liber-viewer2-comment ()
+  (interactive)
+  (let ((section (rt-liber-viewer2-get-section-data)))
+    (when (not section)
+      (error "no section found"))
+    (if (not (featurep 'rt-liberation-gnus))
+       (error "rt-liberation-gnus feature not found")
+      (rt-liber-gnus-compose
+       rt-liber-gnus-comment-address
+       rt-liber-ticket-local
+       `((contents . ,(rt-liber-viewer2-clean-content section)))))))
+
+(defconst rt-liber-viewer2-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "q") 'rt-liber-viewer2-mode-quit)
+    (define-key map (kbd "N") 'rt-liber-viewer2-last-section-in)
+    (define-key map (kbd "n") 'rt-liber-viewer2-next-section-in)
+    (define-key map (kbd "p") 'rt-liber-viewer2-previous-section-in)
+    (define-key map (kbd "V") 'rt-liber-viewer-visit-in-browser)
+    (define-key map (kbd "M") 'rt-liber-viewer2-answer)
+    (define-key map (kbd "C") 'rt-liber-viewer2-comment)
+    (define-key map (kbd "g") 'revert-buffer)
+    (define-key map (kbd "SPC") 'scroll-up)
+    (define-key map (kbd "DEL") 'scroll-down)
+    (define-key map (kbd "h") 'rt-liber-viewer-show-ticket-browser)
+    map)
+  "Key map for ticket viewer2.")
+
+(define-derived-mode rt-liber-viewer2-mode nil
+  "RT Liberation Viewer"
+  "Major Mode for viewing RT tickets.
+\\{rt-liber-viewer-mode-map}"
+  (set
+   (make-local-variable 'font-lock-defaults)
+   '((rt-liber-viewer2-font-lock-keywords)))
+  (set (make-local-variable 'revert-buffer-function)
+       #'rt-liber-viewer2-refresh-ticket-history)
+  (set (make-local-variable 'buffer-stale-function)
+       (lambda (&optional _noconfirm) 'slow))
+  (run-hooks 'rt-liber-viewer-hook))
+
+
 (provide 'rt-liberation)
 
 ;;; rt-liberation.el ends here.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]