0

I would like to have a command that makes selected text invisible, and another to make selected text visible. Currently I have

(buffer-invisibility-spec '(a . t))

(global-set-key (kbd "C-c i") (add-text-properties (region-beginning) (region-end) '(invisible a)))
(global-set-key (kbd "C-c v") (add-text-properties (region-beginning) (region-end) '(invisible nil)))

which works if I run the first line in a buffer, but (buffer-invisibility-spec '(a . t)) throws a void-function error when placed in the init file, (I believe) because there is no buffer yet. I couldn't find any global version of invisibility spec, how would I go about having this load in for every buffer?

1
  • You are trying to bind keys to non-commands. The binding needs to be a function that has an interactive spec, i.e., a command.
    – Drew
    Commented Sep 29, 2019 at 1:27

2 Answers 2

2

buffer-invisibility-spec is a buffer local variable. If you want globally set the key you must add a to the default value of buffer-invisible-spec.

global-set-key is a function therefore the argument (add-text-properties ...) is evaluated before global-set-key. The return value of add-text-properties is undefined in the doc. So you bind your keys to some undefined value which is probably not a function.

You need to define commands that you can bind to keys with global-set-key.

You also need to be more careful when setting the invisible text property. It may be that you destroy already existing settings.

There follows a version that takes care of the above comments:

(eval-when-compile (require 'cl-lib))

(defun my-change-invisible-a (b e &optional add)
  "Make region from B to E invisible if ADD is t.
Otherwise remove invisible spec ni region from B to E."
  (interactive "r")
  (cl-loop
   with iv-prop
   for int being the intervals property 'invisible from b to e
   when (or (null (listp (setq iv-prop (get-text-property (car int) 'invisible))))
        (last iv-prop 0))
   do (setq iv-prop (list iv-prop))
   do
   (setq iv-prop (remove 'a iv-prop))
   (put-text-property (car int) (cdr int)
              'invisible
              (if add
              (cons 'a iv-prop)
            iv-prop))))

(defun my-remove-invisible-a (b e)
  "Remove invisible spec a from region running from B to E."
  (interactive "r")
  (my-change-invisible-a b e nil))

(defun my-add-invisible-a (b e)
  "Add invisible spec a to region from B to E."
  (interactive "r")
  (unless (listp buffer-invisibility-spec)
     (setq buffer-invisibility-spec (list buffer-invisibility-spec)))
  (cl-pushnew '(a . t) buffer-invisibility-spec :test #'equal)
  (my-change-invisible-a b e t))

(global-set-key (kbd "C-c i") #'my-add-invisible-a)
(global-set-key (kbd "C-c v") #'my-remove-invisible-a)

Furthermore, note that it might be that the major mode of the buffer adds invisible to the font-lock-extra-managed-props. In that case one needs to use font-lock to add the invisible specs.

An alternative is using overlays with non-nil invisible specification.

8
  • Ok my, that cl-loop thing!
    – JeanPierre
    Commented Aug 6, 2019 at 11:22
  • @JeanPierre IMHO the cl-loop over intervalls with property and region is the most simple tool in that case. The (last iv-prop 0) is protection against dotted lists. The (remove 'a iv-prop) is a sledgehammer against multiple 'a in iv-prop. The strategy for adding a is removing all and adding once. That unifies that treatment of removing and adding. Transforming the invisible props to lists is also a sledgehammer in the case of removing a. But, I assume here that all other packages handle the invisible text property correctly, i.e., they handle lists as property values well.
    – Tobias
    Commented Aug 6, 2019 at 11:37
  • Copy-pasting this into .emacs gives a void-function error on cl-pushnew Commented Aug 7, 2019 at 15:40
  • @LieAlgebraCow I have added (eval-when-compile (require 'cl-lib)) that should avoid the problem.
    – Tobias
    Commented Aug 7, 2019 at 16:29
  • That works, thanks! Also good to have it make sure not to accidentally break other things, since I would not have handled that on my own. Commented Aug 7, 2019 at 16:34
1

One easy solution is to use libraries zones.el and isearch-prop.el.

  • zones.el is about defining and using sets of zones of contiguous text. In this case, you use only one zone, defined by the region.

  • isearch-prop.el is about isearching text-property or overlay-property contexts. Visibility is just one kind of property.

(You can do the same thing using just isearch-prop.el, but zones makes it even simpler.)

(require 'zones)
(require 'isearch-prop)

(defun hide-region (beg end)
  "Hide the text in the region (make it invisible)."
  (interactive "r")
  (isearchp-make-zones-invisible `((,beg ,end)) nil t))

(defun show-region (beg end)
  "Show the invisible text in the region."
  (interactive "r")
  (isearchp-make-zones-visible `((,beg ,end)) nil t))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.