;;; boon-hl.el --- minor mode for interactive automatic highlighting -*- lexical-binding: t -*-
;; Copyright (C) 2000-2021 Free Software Foundation, Inc.
;; Author: Jean-Philippe Bernardy
;; David M. Koppelman <koppel@ece.lsu.edu>
;; Keywords: faces, minor-mode, matching, display
;; This file is NOT part of GNU Emacs.
;;; Commentary:
;;
;; This is a fork of hi-lock after it was changed to become unusable
;; for searching highlighted patterns (from Emacs 28).
;;; Code:
(require 'hi-lock) ;; for the definition of faces and and helper functions
(require 'dash)
(defvar-local boon-hl-patterns nil
"Patterns provided to boon-hl by user. Should not be changed.")
(put 'boon-hl-patterns 'permanent-local t)
(defcustom boon-hl-face-defaults
'(hi-yellow hi-pink hi-green hi-blue hi-salmon hi-aquamarine
hi-black-b hi-blue-b hi-red-b hi-green-b hi-black-hb)
"Default faces for boon-hl."
:group 'boon
:type '(repeat face))
;;;###autoload
(defun boon-hl-regexp (regexp &optional face)
"Set face of each match REGEXP to FACE using font-lock.
If FACE is nil, choose a face from `boon-hl-face-defaults'
or prompt if universal argument is non-nil. If REGEXP contains
upper case characters (excluding those preceded by `\\') and
`search-upper-case' is non-nil, the matching is case-sensitive."
(interactive
(list
(hi-lock-regexp-okay
(read-regexp "Regexp to highlight" 'regexp-history-last))))
(boon-hl-add regexp face nil
(if (and case-fold-search search-upper-case)
(isearch-no-upper-case-p regexp t)
case-fold-search)
search-whitespace-regexp))
;;;###autoload
(defun boon-hl-symbol (string &optional face)
"`book-hi-lock-regexp' (regexp-quote STRING) FACE.
Additionally, do not mess with case-fold."
(interactive "sSymbol to highlight:")
(boon-hl-add (hi-lock-regexp-okay (format "\\_<%s\\_>" (regexp-quote string)))
face string font-lock-keywords-case-fold-search))
(defvar-local boon-hl--unused-faces nil
"List of faces that is not used and is available for highlighting new text.
Face names from this list come from `boon-hl-face-defaults'.")
;;;###autoload
(defun boon-hl-remove (pattern)
"Remove PATTERN highlight."
(interactive
(list (assoc (completing-read "Unhighlight: "
(-map #'car boon-hl-patterns))
boon-hl-patterns)))
(font-lock-remove-keywords nil (list (plist-get (cdr pattern) :kw)))
(push (plist-get (cdr pattern) :face) boon-hl--unused-faces)
(setq boon-hl-patterns
(delete pattern boon-hl-patterns))
(font-lock-flush))
(defun boon-hl-search (pattern &optional direction limit)
"Search for PATTERN up to LIMIT.
Search backward if DIRECTION is non-nil."
(funcall (car (plist-get (cdr pattern) :kw)) limit direction))
(defun boon-hl-search-backward (pattern &optional limit)
"Search for PATTERN up to LIMIT backward."
(boon-hl-search pattern t limit))
(defun boon--pattern-at (pattern pos limit)
"Search for PATTERN from POS up to LIMIT."
(save-excursion
(goto-char pos)
(boon-hl-search pattern nil limit)))
(defun boon--faces-property (pos)
""
(let ((x (get-text-property pos 'face)))
(if (listp x) x (list x))))
(defun boon-hl-patterns-at-point ()
"List of hl'ed patterns at point."
(--filter
(let* ((pat-face (plist-get (cdr it) :face))
(limit (point))
(pos (if (memq pat-face (boon--faces-property (1- (point))))
(1- (point))
(point))))
(while (memq pat-face (boon--faces-property limit))
(setq limit (next-single-property-change limit 'face)))
(while (and (not (boon--pattern-at it pos limit))
(memq pat-face (boon--faces-property pos)))
(setq pos (previous-single-property-change pos 'face)))
(boon--pattern-at it pos limit))
boon-hl-patterns))
(defun boon-hl-read-face-name ()
"Get face for highlighting.
The next available face. With a prefix argument, read a face
from the minibuffer with completion and history."
(unless boon-hl--unused-faces
(setq boon-hl--unused-faces boon-hl-face-defaults))
(let* ((defaults (append boon-hl--unused-faces
boon-hl-face-defaults))
(face (if current-prefix-arg
(completing-read
(format-prompt "Highlight using face" (car defaults))
obarray 'facep t nil 'face-name-history defaults)
(car defaults))))
;; Update list of unused faces.
(setq boon-hl--unused-faces
(remove face boon-hl--unused-faces))
;; Grow the list of defaults.
(add-to-list 'boon-hl-face-defaults face t)
face))
(defun boon-hl-add (regexp face &optional lighter
case-fold spaces-regexp)
"Highlight SUBEXP of REGEXP with face FACE.
If omitted or nil, SUBEXP defaults to zero, i.e. the entire
REGEXP is highlighted. LIGHTER is a human-readable string to
display instead of a regexp. Non-nil CASE-FOLD ignores case.
SPACES-REGEXP is a regexp to substitute spaces in font-lock search."
(let ((id (list regexp case-fold spaces-regexp)))
(if-let* ((ix (--find-index (equal id (plist-get (cdr it) :id))
boon-hl-patterns)))
(setq boon-hl-patterns
(cons (nth ix boon-hl-patterns)
(-remove-at ix boon-hl-patterns)))
(setq face (or face (boon-hl-read-face-name)))
(let ((kw (list (lambda (limit &optional backward)
(let ((case-fold-search case-fold)
(search-spaces-regexp spaces-regexp))
(if backward
(re-search-backward regexp limit t)
(re-search-forward regexp limit t))))
(list 0 (list 'quote face) 'prepend)))) ;; 0 = subexp
(push (list (or lighter regexp) :kw kw :face face :id id)
boon-hl-patterns)
(font-lock-add-keywords nil (list kw) t)
(font-lock-flush)))))
(provide 'boon-hl)
;;; boon-hl.el ends here