~protesilaos/cursory

88820a72b78863bec28d07a3f9e4a71f55de33cb — Protesilaos Stavrou 3 months ago c906e36
Make presets optionally inherit from another; refactor code
2 files changed, 208 insertions(+), 77 deletions(-)

M README.org
M cursory.el
M README.org => README.org +60 -16
@@ 41,6 41,7 @@ Current development target is {{{development-version}}}.
    + GitHub: <https://github.com/protesilaos/cursory>
    + GitLab: <https://gitlab.com/protesilaos/cursory>
+ Mailing list: <https://lists.sr.ht/~protesilaos/cursory>
+ Backronym: Cursor Usability Requires Styles Objectively Rated Yearlong

#+toc: headlines 8 insert TOC here, with eight headline levels



@@ 69,25 70,30 @@ modify this GNU manual.”
:END:

Cursory provides a thin wrapper around built-in variables that affect
the style of the Emacs cursor on graphical terminals.  The intent is to
allow the user to define preset configurations such as "block with slow
blinking" or "bar with fast blinking" and set them on demand.
the style of the Emacs cursor on graphical terminals.  The intent is
to allow the user to define preset configurations such as "block with
slow blinking" or "bar with fast blinking" and set them on demand.
The use-case for such presets is to adapt to evolving interface
requirements and concomitant levels of expected comfort, such as in
the difference between writing and reading.

#+vindex: cursory-presets
#+findex: cursory-set-preset
The user option ~cursory-presets~ holds the presets.  The command
~cursory-set-preset~ is used to select one among them.  Selection
supports minibuffer completion when there are multiple presets, else
sets the single preset outright.
~cursory-set-preset~ is applies one among them.  The command supports
minibuffer completion when there are multiple presets, else sets the
single preset outright.

Presets consist of a list of properties that govern the cursor type in
the active and inactive windows, as well as cursor blinking variables.
Presets consist of an arbitrary symbol broadly described the style set
followed by a list of properties that govern the cursor type in the
active and inactive windows, as well as cursor blinking variables.
They look like this:

#+begin_src emacs-lisp
(bar
 :cursor-type (bar . 2)
 :cursor-in-non-selected-windows hollow
 :blink-cursor-mode 1
 :blink-cursor-blinks 10
 :blink-cursor-interval 0.5
 :blink-cursor-delay 0.2)


@@ 102,14 108,40 @@ references.

A property of =:blink-cursor-mode= is also available.  It is a numeric
value of either =1= or =-1= and is given to the function
~blink-cursor-mode~ (=1= is to enable, =-1= is to disable the mode).
~blink-cursor-mode~: =1= is to enable, =-1= is to disable the mode.

A preset whose car is =t= is treated as the default option.  This makes
it possible to specify multiple presets without duplicating their
properties.  The other presets beside =t= act as overrides of the
defaults and, as such, need only consist of the properties that change
from the default.  See the original value of this variable for how that
is done:
Presets can inherit from each other.  Using the special =:inherit=
property, like this:

#+begin_src emacs-lisp
(bar
 :cursor-type (bar . 2)
 :cursor-in-non-selected-windows hollow
 :blink-cursor-mode 1
 :blink-cursor-blinks 10
 :blink-cursor-interval 0.5
 :blink-cursor-delay 0.2)
(bar-no-other-window
 :inherit bar
 :cursor-in-non-selected-windows nil)
#+end_src

In the above example, the =bar-no-other-window= is the same as =bar=
except for the value of =:cursor-in-non-selected-windows=.

The value given to the =:inherit= property corresponds to the name of
another named preset (unquoted).  This tells the relevant Cursory
functions to get the properties of that given preset and blend them
with those of the current one.  The properties of the current preset
take precedence over those of the inherited one, thus overriding them.

A preset whose car is =t= is treated as the default option.  This
makes it possible to specify multiple presets without duplicating
their properties.  The other presets beside =t= act as overrides of
the defaults and, as such, need only consist of the properties that
change from the default.  In the case of an =:inherit=, properties are
first taken from the inherited preset and then the default one.  See
the original value of this variable for how that is done:

#+begin_src emacs-lisp
(defcustom cursory-presets


@@ 120,9 152,15 @@ is done:
    (bar
     :cursor-type (bar . 2)
     :blink-cursor-interval 0.5)
    (bar-no-other-window
     :inherit bar
     :cursor-in-non-selected-windows nil)
    (underscore
     :cursor-type (hbar . 3)
     :blink-cursor-blinks 50)
    (underscore-thin-other-window
     :inherit underscore
     :cursor-in-non-selected-windows (hbar . 1))
    (t ; the default values
     :cursor-type box
     :cursor-in-non-selected-windows hollow


@@ 145,7 183,11 @@ The default behaviour of ~cursory-set-preset~ is to change cursors
globally.  The user can, however, limit the effect to the current
buffer.  With interactive use, this is done by invoking the command with
a universal prefix argument (=C-u= by default).  When called from Lisp,
the LOCAL argument must be non-nil.
the LOCAL argument must be non-nil, thus:

#+begin_src emacs-lisp
(cursory-set-preset 'bar :local)
#+end_src

#+findex: cursory-store-latest-preset
#+vindex: cursory-latest-state-file


@@ 154,6 196,8 @@ The function ~cursory-store-latest-preset~ is used to save the last
selected style in the ~cursory-latest-state-file~.  The value can then
be restored with the ~cursory-restore-latest-preset~ function.

[[#h:b3c38cda-48d3-4715-9c46-6b9844a6da86][Sample configuration]].

* Installation
:PROPERTIES:
:CUSTOM_ID: h:6dd596f2-d98b-4275-b25e-495e2a0616bf

M cursory.el => cursory.el +148 -61
@@ 28,22 28,27 @@
;;; Commentary:
;;
;; Cursory provides a thin wrapper around built-in variables that affect
;; the style of the Emacs cursor on graphical terminals.  The intent is to
;; allow the user to define preset configurations such as "block with slow
;; blinking" or "bar with fast blinking" and set them on demand.
;; the style of the Emacs cursor on graphical terminals.  The intent is
;; to allow the user to define preset configurations such as "block with
;; slow blinking" or "bar with fast blinking" and set them on demand.
;; The use-case for such presets is to adapt to evolving interface
;; requirements and concomitant levels of expected comfort, such as in
;; the difference between writing and reading.
;;
;; The user option `cursory-presets' holds the presets.  The command
;; `cursory-set-preset' is used to select one among them.  Selection
;; supports minibuffer completion when there are multiple presets, else
;; sets the single preset outright.
;; `cursory-set-preset' is applies one among them.  The command supports
;; minibuffer completion when there are multiple presets, else sets the
;; single preset outright.
;;
;; Presets consist of a list of properties that govern the cursor type in
;; the active and inactive windows, as well as cursor blinking variables.
;; Presets consist of an arbitrary symbol broadly described the style set
;; followed by a list of properties that govern the cursor type in the
;; active and inactive windows, as well as cursor blinking variables.
;; They look like this:
;;
;;     (bar
;;      :cursor-type (bar . 2)
;;      :cursor-in-non-selected-windows hollow
;;      :blink-cursor-mode 1
;;      :blink-cursor-blinks 10
;;      :blink-cursor-interval 0.5
;;      :blink-cursor-delay 0.2)


@@ 57,14 62,39 @@
;;
;; A property of `:blink-cursor-mode' is also available.  It is a numeric
;; value of either `1' or `-1' and is given to the function
;; `blink-cursor-mode' (`1' is to enable, `-1' is to disable the mode).
;; `blink-cursor-mode': `1' is to enable, `-1' is to disable the mode.
;;
;; A preset whose car is `t' is treated as the default option.  This makes
;; it possible to specify multiple presets without duplicating their
;; properties.  The other presets beside `t' act as overrides of the
;; defaults and, as such, need only consist of the properties that change
;; from the default.  See the original value of this variable for how that
;; is done:
;; Presets can inherit from each other.  Using the special `:inherit'
;; property, like this:
;;
;;     (bar
;;      :cursor-type (bar . 2)
;;      :cursor-in-non-selected-windows hollow
;;      :blink-cursor-mode 1
;;      :blink-cursor-blinks 10
;;      :blink-cursor-interval 0.5
;;      :blink-cursor-delay 0.2)
;;
;;     (bar-no-other-window
;;      :inherit bar
;;      :cursor-in-non-selected-windows nil)
;;
;; In the above example, the `bar-no-other-window' is the same as `bar'
;; except for the value of `:cursor-in-non-selected-windows'.
;;
;; The value given to the `:inherit' property corresponds to the name of
;; another named preset (unquoted).  This tells the relevant Cursory
;; functions to get the properties of that given preset and blend them
;; with those of the current one.  The properties of the current preset
;; take precedence over those of the inherited one, thus overriding them.
;;
;; A preset whose car is `t' is treated as the default option.  This
;; makes it possible to specify multiple presets without duplicating
;; their properties.  The other presets beside `t' act as overrides of
;; the defaults and, as such, need only consist of the properties that
;; change from the default.  In the case of an `:inherit', properties are
;; first taken from the inherited preset and then the default one.  See
;; the original value of this variable for how that is done:
;;
;;     (defcustom cursory-presets
;;       '((box


@@ 74,9 104,15 @@
;;         (bar
;;          :cursor-type (bar . 2)
;;          :blink-cursor-interval 0.5)
;;         (bar-no-other-window
;;          :inherit bar
;;          :cursor-in-non-selected-windows nil)
;;         (underscore
;;          :cursor-type (hbar . 3)
;;          :blink-cursor-blinks 50)
;;         (underscore-thin-other-window
;;          :inherit underscore
;;          :cursor-in-non-selected-windows (hbar . 1))
;;         (t ; the default values
;;          :cursor-type box
;;          :cursor-in-non-selected-windows hollow


@@ 96,7 132,9 @@
;; globally.  The user can, however, limit the effect to the current
;; buffer.  With interactive use, this is done by invoking the command with
;; a universal prefix argument (`C-u' by default).  When called from Lisp,
;; the LOCAL argument must be non-nil.
;; the LOCAL argument must be non-nil, thus:
;;
;;     (cursory-set-preset 'bar :local)
;;
;; The function `cursory-store-latest-preset' is used to save the last
;; selected style in the `cursory-latest-state-file'.  The value can then


@@ 117,9 155,15 @@
    (bar
     :cursor-type (bar . 2)
     :blink-cursor-interval 0.5)
    (bar-no-other-window
     :inherit bar
     :cursor-in-non-selected-windows nil)
    (underscore
     :cursor-type (hbar . 3)
     :blink-cursor-blinks 50)
    (underscore-thin-other-window
     :inherit underscore
     :cursor-in-non-selected-windows (hbar . 1))
    (t ; the default values
     :cursor-type box
     :cursor-in-non-selected-windows hollow


@@ 140,7 184,7 @@ the defaults and, as such, need only consist of the properties
that change from the default.  See the original value of this
variable for how that is done.

The cdr is a plist which specifies the cursor type and blink
The `cdr' is a plist which specifies the cursor type and blink
properties.  In particular, it accepts the following properties:

    :cursor-type


@@ 156,9 200,23 @@ them accepts is the same as the variable it references.

A property of `:blink-cursor-mode' is also available.  It is a
numeric value of either 1 or -1 and is given to the function
`blink-cursor-mode' (1 is to enable, -1 is to disable the mode)."
`blink-cursor-mode' (1 is to enable, -1 is to disable the mode).

Finally, the plist takes the special `:inherit' property.  Its
value is contains the name of another named preset (unquoted).
This tells the relevant Cursory functions to get the properties
of that given preset and blend them with those of the current
one.  The properties of the current preset take precedence over
those of the inherited one, thus overriding them.  In practice,
this is a way to have something like an underscore style with a
hallow cursor for the other window and the same with a thin
underscore for the other window (see the default value of this
user option for concrete examples).  Remember that all named
presets fall back to the preset whose name is t.  The `:inherit'
is not a substitute for that generic fallback but rather an extra
method of specifying font configuration presets."
  :group 'cursory
  :package-version '(cursory . "0.3.0")
  :package-version '(cursory . "1.0.0")
  :type `(alist
          :value-type
          (plist :options


@@ 181,7 239,8 @@ numeric value of either 1 or -1 and is given to the function
                          :blink-cursor-mode)
                   (choice :value 1
                           (const :tag "Enable" 1)
                           (const :tag "Disable" -1)))))
                           (const :tag "Disable" -1)))
                  ((const :tag "Inherit another preset" :inherit) symbol)))
          :key-type symbol))

(defcustom cursory-latest-state-file


@@ 195,19 254,31 @@ Saving is done by the `cursory-store-latest-preset' function."
(defvar cursory--style-hist '()
  "Minibuffer history of `cursory--set-cursor-prompt'.")

(defun cursory--preset-values (preset)
  "Get properties of PRESET with relevant fallbacks."
  (append (alist-get preset cursory-presets)
          (alist-get t cursory-presets)))
(defun cursory--preset-p (preset)
  "Return non-nil if PRESET is one of the named `cursory-presets'."
  (let ((presets (delq t (mapcar #'car cursory-presets))))
    (memq preset presets)))

(defun cursory--get-inherit-name (preset)
  "Get the `:inherit' value of PRESET."
  (when-let* ((inherit (plist-get (alist-get preset cursory-presets) :inherit))
              (cursory--preset-p inherit))
    inherit))

(defun cursory--get-preset-properties (preset)
  "Return list of properties for PRESET in `cursory-presets'."
  (let ((presets cursory-presets))
    (append (alist-get preset presets)
            (when-let ((inherit (cursory--get-inherit-name preset)))
              (alist-get inherit presets))
            (alist-get t presets))))

(defun cursory--presets-no-fallback ()
  "Return list of `cursory-presets', minus the fallback value."
  (delete
   nil
   (mapcar (lambda (symbol)
             (unless (eq (car symbol) t)
               symbol))
           cursory-presets)))
  (seq-remove
   (lambda (symbol)
     (eq (car symbol) t))
   cursory-presets))

(defun cursory--set-cursor-prompt ()
  "Promp for `cursory-presets' (used by `cursory-set-preset')."


@@ 220,6 291,48 @@ Saving is done by the `cursory-store-latest-preset' function."
     (cursory--presets-no-fallback)
     nil t nil 'cursory--style-hist def)))

(defun cursory--get-preset-as-symbol (preset)
  "Return PRESET as a symbol."
  (if (stringp preset)
      (intern preset)
    preset))

(defun cursory--delete-local-variables ()
  "Delete all cursor-related local variables."
  (dolist (var '( cursor-type cursor-in-non-selected-windows
                  blink-cursor-blinks blink-cursor-interval
                  blink-cursor-delay))
    (kill-local-variable var)))

(defun cursory--set-preset-with-scope (preset &optional local)
  "Set PRESET of `cursory-presets' to the global scope.
With optional non-nil LOCAL, set STYLES scoped locally to the
current buffer."
  (if-let ((styles (cursory--get-preset-properties preset)))
      ;; We do not include this in the `if-let' because we also accept
      ;; nil values for :cursor-type, :cursor-in-non-selected-windows.
      (let ((type (plist-get styles :cursor-type))
            (type-no-select (plist-get styles :cursor-in-non-selected-windows))
            (blinks (plist-get styles :blink-cursor-blinks))
            (interval (plist-get styles :blink-cursor-interval))
            (delay (plist-get styles :blink-cursor-delay)))
        (if local
            (setq-local cursor-type type
                        cursor-in-non-selected-windows type-no-select
                        blink-cursor-blinks blinks
                        blink-cursor-interval interval
                        blink-cursor-delay delay)
          (cursory--delete-local-variables)
          (setq-default cursor-type type
                        cursor-in-non-selected-windows type-no-select
                        blink-cursor-blinks blinks
                        blink-cursor-interval interval
                        blink-cursor-delay delay)
          ;; We only want to save global values in `cursory-store-latest-preset'.
          (add-to-history 'cursory--style-hist (format "%s" preset)))
        (blink-cursor-mode (plist-get styles :blink-cursor-mode)))
    (user-error "Cannot determine styles of preset `%s'" preset)))

;;;###autoload
(defun cursory-set-preset (style &optional local)
  "Set cursor preset associated with STYLE.


@@ 229,40 342,14 @@ STYLE is a symbol that represents the car of a list in

With optional LOCAL as a prefix argument, set the
`cursory-presets' only for the current buffer.  This does not
cover the function `blink-cursor-mode', which is always global."
cover the `blink-cursor-mode', which is always global."
  (interactive
   (list
    (if (= (length cursory-presets) 1)
        (caar cursory-presets)
      (cursory--set-cursor-prompt))
    (cursory--set-cursor-prompt)
    current-prefix-arg))
  (when-let* ((styles (if (stringp style) (intern style) style))
              (properties (cursory--preset-values styles))
              (type (plist-get properties :cursor-type))
              (type-no-select (plist-get properties :cursor-in-non-selected-windows))
              (blinks (plist-get properties :blink-cursor-blinks))
              (interval (plist-get properties :blink-cursor-interval))
              (delay (plist-get properties :blink-cursor-delay)))
    (if local
        (setq-local cursor-type type
                    cursor-in-non-selected-windows type-no-select
                    blink-cursor-blinks blinks
                    blink-cursor-interval interval
                    blink-cursor-delay delay)
      (dolist (var '( cursor-type cursor-in-non-selected-windows
                      blink-cursor-blinks blink-cursor-interval
                      blink-cursor-delay))
        (kill-local-variable var))
      (setq-default cursor-type type
                    cursor-in-non-selected-windows type-no-select
                    blink-cursor-blinks blinks
                    blink-cursor-interval interval
                    blink-cursor-delay delay))
    (blink-cursor-mode (plist-get properties :blink-cursor-mode))
    ;; We only want to save global values in
    ;; `cursory-store-latest-preset'.
    (unless local
      (add-to-history 'cursory--style-hist (format "%s" style)))))
  (if-let ((preset (cursory--get-preset-as-symbol style)))
      (cursory--set-preset-with-scope preset local)
    (user-error "Cannot determine preset `%s'" preset)))

;;;###autoload
(defun cursory-store-latest-preset ()