1

I am looking for some guidance, please, to reduce the time needed to perform my custom overlay removal function. The delay of up to 0.1 seconds is caused because a plethora of variables all have values, however, not every variable is necessarily used.

My goal is to attach a second variable that can be set to non-nil whenever the first variable is used, but I am unsure how to set this up and how to incorporate that into the overlay removal function.

Perhaps something that looks like this would be useful:

  • if (variable-one . t), then (remove-overlays (point-min) (point-max) 'display character)

In the following example, M-x sub-char-mode will place an overlay over the characters 1, 2 or 3 whenever the cursor is visiting any of those characters:

  • 1 will become |1

  • 2 will become |2

  • 3 will become |3

(defvar variable-one (concat
  (propertize (char-to-string ?\u007C)
    'face 'font-lock-warning-face
    'cursor t)
  (propertize "1" 'face 'highlight 'cursor t) ))

(defvar variable-one-p (cons variable-one nil))

(defvar variable-two (concat
  (propertize (char-to-string ?\u007C)
    'face 'font-lock-warning-face
    'cursor t)
  (propertize "2" 'face 'highlight 'cursor t) ))

(defvar variable-two-p (cons variable-two nil))

(defvar variable-three (concat
  (propertize (char-to-string ?\u007C)
    'face 'font-lock-warning-face
    'cursor t)
  (propertize "3" 'face 'highlight 'cursor t) ))

(defvar variable-three-p (cons variable-three nil))

(defun substitute-character ()
  (cond
    ((eq (char-after (point)) 49)
      (setq variable-one-p (cons variable-one t))
      (overlay-put (make-overlay (point) (1+ (point))) 'display variable-one))
    ((eq (char-after (point)) 50)
      (setq variable-two-p (cons variable-two t))
      (overlay-put (make-overlay (point) (1+ (point))) 'display variable-two))
    ((eq (char-after (point)) 51)
      (setq variable-three-p (cons variable-three t))
      (overlay-put (make-overlay (point) (1+ (point))) 'display variable-three))))

(defun remove-sub-char ()
  (dolist (character `(
      ,variable-one
      ,variable-two
      ,variable-three))
    (remove-overlays (point-min) (point-max) 'display character))
 (dolist (my-variable `(
      ,variable-one-p
      ,variable-two-p
      ,variable-three-p))
    (setq my-variable nil)) )

(defun sub-char-post-command-hook ()
  (remove-sub-char)
  (substitute-character))

(define-minor-mode sub-char-mode
"A minor-mode for testing overlay-removal with cons cells."
  :init-value nil
  :lighter " OV-REMOVE"
  :keymap nil
  :global nil
  :group 'lawlist
  (cond
    (sub-char-mode
      (add-hook 'post-command-hook 'sub-char-post-command-hook nil t)
      (message "Turned ON `sub-char-mode`."))
    (t
      (remove-hook 'post-command-hook 'sub-char-post-command-hook t)
      (remove-sub-char)
      (message "Turned OFF `sub-char-mode`."))))

Apologies for pasting this image here - feel free to remove it. But I couldn't paste it into a comment, to reply to your comment asking for the appearance. This is vline-style = compose and col-highlight-vline-face-flag = nil:

enter image description here

Drew
  • 29,895
  • 7
  • 74
  • 104
lawlist
  • 13,099
  • 3
  • 49
  • 158
  • You should pose a question... (Wrt performance, have you tried profiling the code, to see where the biggest slowdowns are?) – Drew Aug 24 '14 at 02:08
  • @Drew -- thank you for taking a look at this thread. By using a `measure-time` macro, I have verified that the problem is due to the fact that I have more than 750 variables with values. The function `remove-overlays` is being used in conjunction with `dolist` and it attempts to remove 750+ overlays from `point-min` to `point-max` every `post-command-hook` loop. If I can set a second variable to `t` whenever the first variable is used, then in theory I could use something like `(if (cdr (variable-one . t)`, then `remove-overlays variable-one`. But, I've never seen that done before. – lawlist Aug 24 '14 at 02:22
  • I see. I have no special expertise to offer here. Maybe you can cache some info and not repeat everything after each command? And have you thought of just using the `display-table` to change the appearance of your characters? What you are doing seems pretty heavy-handed - but again, I'm no expert on these things. Hopefully someone will give you some real help. – Drew Aug 24 '14 at 02:27
  • @Drew -- This issue is related to my on-going pet project for drawing cross-hairs -- essentially, it is your col-highlight + v-line + concatenating a zero-width vertical bar (with color) [`?\uFEFF`] and slightly shrinking the character to the right, which creates a vertical line the length of the buffer window. You are right, it is heavy-handed and is thus on an idle-timer because of the considerable time involved. While `display-table` would affect the whole buffer, perhaps using a cache will help speed things up. I'll read-up on that aspect. – lawlist Aug 24 '14 at 02:36
  • I see. Don't let me discourage you in that. Clearly, focusing on a piece of a larger project in a S.O. question can give the impression that things are heavy-handed for just that focused question. BTW, have you considered the [**Crosshairs**](http://www.emacswiki.org/CrosshairHighlighting) library ([`crosshairs.el`](http://www.emacswiki.org/emacs-en/download/crosshairs.el))? – Drew Aug 24 '14 at 15:08
  • @Drew -- thank you for the referral to the `crosshairs.el` library. The character wide vertical highlight is a viable alternative, but I prefer a thin vertical line that aligns to the left of the `current-column`. However, that visual effect can only be achieved by concatenating a zero-width vertical bar (with color) `\uFEFF` and slightly shrinking the character to the right (so that the visual length of everything to the right remains the same). I have posted a draft answer that I believe resolves the issue so that unused variables will get skipped over during the overlay removal process. – lawlist Aug 24 '14 at 18:27
  • FWIW, you can get a thin vertical line such as you describe with `crosshairs.el` as well, by just setting option `vline-style` to `compose` and option `col-highlight-vline-face-flag` to nil. (Well, a difference is that the thin line aligns on the center of the current column, not to the left of it.) – Drew Aug 24 '14 at 21:16
  • @Drew -- One of the differences between my pet minor-mode project and the publicly available vline-type libraries is that my vertical line is a solid thin line from top to bottom of the window, *including* the areas where there is text. The `vline-style 'compose` option does not place overlays where there is text, so the ruler is not solid. One of the drawbacks with my pet project is that the text containing the vertical line must necessarily shrink in size so that the horizontal length of text does not change visually. Here is a link screen-shot: http://stackoverflow.com/a/23813217/2112489 – lawlist Aug 24 '14 at 21:48
  • Dunno why you say the *the `vline-style` `compose` option does not place overlays where there is text.* It certainly does. (And it places the line also where there is no text.) The thin vertical line does not cover interline space, so it is in that sense a broken (dashed) line, but it certainly does run through text characters. Perhaps it could be improved to pass also through the interline spacing. – Drew Aug 24 '14 at 21:56
  • @Drew -- perhaps there is another setting that I'm missing. Out of the box, setting `vline-style 'compose` and `col-highlight-vline-face-flag nil` on OSX with an August 15, 2014 build of Emacs Trunk only places a centered vertical line over whitespace. I would love to see sample code that can create a vertical strike-through of a character -- I even submitted a feature request to the Emacs team about a month or so ago. Whenever you have some free time, if you'd like to take a stab at resolving that related thread, it would be greatly appreciated: http://stackoverflow.com/q/23744237/2112489 – lawlist Aug 24 '14 at 22:17
  • I've pasted an image of what I see. It shows a thin vertical line passing through the text. And it also shows that the line is "dashed": it is interrupted at the interline spacing. – Drew Aug 24 '14 at 22:31
  • @Drew -- that is truly amazing -- I've never seen my Emacs do that. I'll work on it later this evening to see if there is anything I can do to reproduce an effect as depicted in your screenshot. – lawlist Aug 24 '14 at 22:35
  • If you don't get that out of the box with the libraries I mentioned, perhaps send a bug report somewhere - e.g. to the vline.el author. Maybe it's a platform problem. From `emacs -Q` on MS Windows (Emacs 24 dev snapshot, but other builds look the same), I just loadded libs vline, hl-line+ (which loads hl-line), col-highlight, and crosshairs, and I set the two options as indicated. – Drew Aug 24 '14 at 22:43
  • Sorry, I'm no expert on the vertical-line overlays. That's from the `vline.el` code. You might want to take a look at that code. – Drew Aug 24 '14 at 22:46
  • @Drew -- you are right -- I just tried vline-mode on Windows XP through Parallels on OSX, and the vertical strike-through works out of the box with `vline-style 'compose`. I wonder why the OSX GUI version of Emacs can't do it. – lawlist Aug 24 '14 at 22:51
  • 1
    Time to investigate. And then maybe fire off a bug report. If you do, that will help others too. – Drew Aug 24 '14 at 22:51
  • @Drew -- I was able to achieve a vertical strike-through effect on OSX with `vline-mode` using certain fonts / sizes, e.g., `:font "DejaVu Sans Mono-18"`. Thank you for letting me know that this was possible -- greatly appreciated! :) I had been using `:font "-*-Courier-normal-normal-normal-*-18-*-*-*-m-0-iso10646-1"`, and that doesn't work with `vline-mode` strike-through on OSX. – lawlist Aug 25 '14 at 02:24
  • Perhaps you can characterize the fonts for which it works or does not work? Or even maybe determine why it sometimes does not work? If not, maybe it would nevertheless be good to let the author know, so he can look into it if he is interested. – Drew Aug 25 '14 at 02:25
  • FWIW, it works on Windows with `emacs -Q`, and that uses this font (for Emacs 24): "-outline-Courier New-normal-normal-normal-mono-17-*-*-*-c-*-iso8859-1" – Drew Aug 25 '14 at 02:27

2 Answers2

0

First Draft (August 24, 2014):  The first draft answer defines the variables as a cons cell -- the car is a predetermined overlay string, and the cdr is nil. When the cursor visits the characters 1, 2 or 3, an overlay is placed on top of those characters and the cdr of the applicable cons cell is set to t by using setcdr. The overlay removal function contains a list of variable names and the corresponding cons cells -- when the cdr of the cons cell is non-nil (i.e., t), the overlay is removed, and the cdr of the applicable cons cell is set back to nil using setcdr. The advantage of this type of setup is that the overlay removal function will quickly look at and then skip over variables whose cons cell cdr is nil -- thus saving a substantial amount of time when dealing with large quantities of variables with predetermined overlay strings.

EDIT (August 26, 2014):  Modified code to permit using the same variable names in different buffers and set buffer-local values. Related threads are: How to use `setcdr` with buffer-local variables and Incorporate variable name into `dolist` cycle and change its value .

(defvar variable-one
  (cons
    (concat
      (propertize (char-to-string ?\u007C)
        'face 'font-lock-warning-face
        'cursor t)
      (propertize "1" 'face 'highlight 'cursor t) )
    nil))

(make-variable-buffer-local 'variable-one)

(defvar variable-two
  (cons
    (concat
     (propertize (char-to-string ?\u007C)
       'face 'font-lock-warning-face
       'cursor t)
     (propertize "2" 'face 'highlight 'cursor t) )
    nil))

(make-variable-buffer-local 'variable-two)

(defvar variable-three
  (cons
    (concat
     (propertize (char-to-string ?\u007C)
       'face 'font-lock-warning-face
       'cursor t)
     (propertize "3" 'face 'highlight 'cursor t) )
  nil))

(make-variable-buffer-local 'variable-three)

(defun sub-char ()
  (cond
    ((eq (char-after (point)) 49)
      (let ((newlist (copy-list variable-one)))
        (setcdr newlist t)
        (setq-local variable-one newlist)
        (overlay-put (make-overlay (point) (1+ (point))) 'display (car variable-one))))
    ((eq (char-after (point)) 50)
      (let ((newlist (copy-list variable-two)))
        (setcdr newlist t)
        (setq-local variable-two newlist)
        (overlay-put (make-overlay (point) (1+ (point))) 'display (car variable-two))))
    ((eq (char-after (point)) 51)
      (let ((newlist (copy-list variable-three)))
        (setcdr newlist t)
        (setq-local variable-three newlist)
        (overlay-put (make-overlay (point) (1+ (point))) 'display (car variable-three))))))

(defun remove-sub-char ()
  (dolist (character `(
      (variable-one ,variable-one)
      (variable-two ,variable-two)
      (variable-three ,variable-three)))
    (when (cdr (car (cdr character)))
      (let* (
          (var (car character))
          (newlist (copy-list (car (cdr character)))) )
        (remove-overlays (point-min) (point-max) 'display (car (car (cdr character))))
        (setcdr newlist nil)
        (set (car character) newlist)
        (message "var1: %s | var2: %s | var3: %s" variable-one variable-two variable-three) ))))

(defun sub-char-post-command-hook ()
  (remove-sub-char)
  (sub-char))

(define-minor-mode sub-char-mode
"A minor-mode for testing overlay-removal with cons cells."
  :init-value nil
  :lighter " OV-REMOVE"
  :keymap nil
  :global nil
  :group 'lawlist
  (cond
    (sub-char-mode
      (add-hook 'post-command-hook 'sub-char-post-command-hook nil t)
      (message "Turned ON `sub-char-mode`."))
    (t
      (remove-hook 'post-command-hook 'sub-char-post-command-hook t)
      (remove-sub-char)
      (message "Turned OFF `sub-char-mode`."))))
Community
  • 1
  • 1
lawlist
  • 13,099
  • 3
  • 49
  • 158
0

I suggest you start by getting rid of your variable-FOO-p vars: not only their names are wrong (the "-p" suffix is meant for use with predicates which are necessarily functions, and not variables) but they're unneeded. Rather than tell remove-overlays to remove all overlays with a particular display property, just add a property of your own (e.g. (overlay-put <youroverlay> 'vline t)) so you can then do a single (remove-overlays (point-min) (point-max) 'vline t). Of course, another approach which will work at least as well is to keep a single buffer-local variable (better yet, window-local) which holds a list of all the overlays you currently have placed. This way, you can remove those overlays with a single (mapc #'delete-overlay vline--overlays), which is more efficient. It can be made even more efficient by moving/reusing those overlays rather than deleting them and then creating new ones instead.

Stefan
  • 27,908
  • 4
  • 53
  • 82