unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [ELPA] New package proposal: visual-path-abbrev.el
@ 2019-03-02 11:05 Tassilo Horn
  2019-03-02 11:34 ` Eli Zaretskii
  2019-03-02 21:25 ` Leo Liu
  0 siblings, 2 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-02 11:05 UTC (permalink / raw)
  To: emacs-devel

Hi all,

I've just written a small minor mode which abbreviates file paths
visually by using overlays.  When point enters such an overlay, the path
is shown normally again.

I wrote it mostly because at work our java code has a very deeply nested
package structure which forced me to make my emacs frame running Magit
(listing all modified files) wider than I like it in normal use.

This package has been written over the last few hours and has no home
yet.  If that sounds good, I'd like to add it to ELPA (and just to
ELPA).

Also, suggestions for the code are welcome.  Especially, there are two
known problems:

- Sometimes when scrolling fast and then stopping, only parts of the
  visible buffer portion got the overlays applied.  You can try
  triggering that problem by enabling the mode in a *grep* buffer and
  then scrolling a long way.
  
- When lines are wrapped around and line-move-visual is t, the mode can
  make the line short enough so that it doesn't wrap anymore.  But
  still next-line moves point to where it would belong if the mode were
  not active, i.e., point jumps to somewhere on the same line.
  
Bye,
Tassilo

--8<---------------cut here---------------start------------->8---
;;; visual-path-abbrev.el --- Visually abbreviate paths  -*- lexical-binding: t; -*-

;; Copyright (C) 2019 Free Software Foundation, Inc

;; Author: Tassilo Horn <tsdh@gnu.org>
;; Keywords: TODO
;; Version: TODO

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; This minor mode abbreviates the directory part of file file paths by using
;; overlays.  For example, a longish path like
;;
;;    /home/myuser/Documents/Letters/Personal-Family/Letter-to-John.tex
;;
;; will be displayed like this:
;;
;;   /h…/m…/D…/L…/P…-F…/Letter-to-John.tex
;;
;; By default, the abbreviate display is disabled when point enters the overlay
;; so that you can edit the path normally.  Also, abbreviated path are only
;; shown if the abbreviation as actually shorter as the original path (which
;; depends on what you add as replacement).
;;
;; There's stuff to customize, just check `M-x customize-group RET
;; visual-path-abbrev RET'.

;;; Code:

(require 'seq)

(defgroup visual-path-abbrev nil
  "Visually abbreviate the directory part of paths.")

(defcustom visual-path-abbrev-regex
  (concat "\\(?:file://\\)?/?"
	  "\\(?:[[:alnum:]@_.-]+/\\)+[[:alnum:]@_.-]*\\.\\w+")
  "Regexp matching paths.")

(defcustom visual-path-abbrev-replace-regex
  "[.@]?[[:alnum:]]\\([[:alnum:]]+\\)[-_/.]"
  "Regexp which will be visually replaced in paths.
All matches of this regexp's group number 1 in the paths matching
`visual-path-abbrev-regex' will be replaced by
`visual-path-abbrev-abbrev'.")

(defcustom visual-path-abbrev-abbrev "…"
  "String to be displayed instead of the match group 1 of
`visual-path-abbrev-regex'.")

(defun visual-path-abbrev--get-abbrev (path)
  (let ((file (file-name-nondirectory path))
	(dir (file-name-directory path)))
    (concat
     (file-name-as-directory
      (replace-regexp-in-string
       visual-path-abbrev-replace-regex
       visual-path-abbrev-abbrev dir nil nil 1))
     file)))

(defun visual-path-abbrev--not-on-overlay-p (_buffer pos path abbrev)
  (when-let ((ol (car (seq-filter
		       (lambda (o) (overlay-get o 'visual-path-abbrev))
		       (overlays-at pos)))))
    (or (< (point) (overlay-start ol))
	(> (point) (overlay-end ol)))))

(defun visual-path-abbrev--abbrev-shorter-p (_buffer _pos path abbrev)
  (< (string-width abbrev)
     (string-width path)))

(defvar visual-path-abbrev-display-predicates
  (list #'visual-path-abbrev--not-on-overlay-p
	#'visual-path-abbrev--abbrev-shorter-p))

(defun visual-path-abbrev--display-p (buffer pos path abbrev)
  (seq-every-p (lambda (pred)
		 (funcall pred buffer pos path abbrev))
	       visual-path-abbrev-display-predicates))

(defun visual-path-abbrev--delete-overlays (beg end)
  (dolist (ol (overlays-in beg end))
    (when (overlay-get ol 'visual-path-abbrev)
      (delete-overlay ol))))

(defun visual-path-abbrev--place-overlays (&rest _ignored)
  (save-excursion
    (let ((ws (window-start))
	  (we (window-end)))
      (visual-path-abbrev--delete-overlays ws we)
      (goto-char ws)
      (while (re-search-forward visual-path-abbrev-regex we t)
	(let* ((beg (match-beginning 0))
	       (end (match-end 0))
	       (path (match-string 0))
	       (ol (make-overlay beg end nil t))
	       (abbrev (visual-path-abbrev--get-abbrev path)))
	  (overlay-put ol 'visual-path-abbrev t)
	  (overlay-put
	   ol 'display `(when (visual-path-abbrev--display-p
			       object buffer-position ,path ,abbrev)
			  . ,abbrev))
	  (overlay-put ol 'help-echo path))))))

(define-minor-mode visual-path-abbrev-mode
  "Visually abbreviate file paths."
  nil " VPAbbr" nil
  (if visual-path-abbrev-mode
      (progn
	(add-hook 'post-command-hook
		  #'visual-path-abbrev--place-overlays
		  t t)
	(visual-path-abbrev--place-overlays))
    (remove-hook 'post-command-hook
		 #'visual-path-abbrev--place-overlays
		 t)
    (visual-path-abbrev--delete-overlays 1 (1+ (buffer-size)))))
--8<---------------cut here---------------end--------------->8---



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-02 11:05 [ELPA] New package proposal: visual-path-abbrev.el Tassilo Horn
@ 2019-03-02 11:34 ` Eli Zaretskii
  2019-03-02 14:59   ` Tassilo Horn
  2019-03-03  9:46   ` Tassilo Horn
  2019-03-02 21:25 ` Leo Liu
  1 sibling, 2 replies; 23+ messages in thread
From: Eli Zaretskii @ 2019-03-02 11:34 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

> From: Tassilo Horn <tsdh@gnu.org>
> Date: Sat, 02 Mar 2019 12:05:50 +0100
> 
> I've just written a small minor mode which abbreviates file paths
> visually by using overlays.  When point enters such an overlay, the path
> is shown normally again.

The GNU Coding Style conventions frown on using "path" for anything
but PATH-style directory lists.  I think you mean "file names" here.

> - Sometimes when scrolling fast and then stopping, only parts of the
>   visible buffer portion got the overlays applied.  You can try
>   triggering that problem by enabling the mode in a *grep* buffer and
>   then scrolling a long way.

Your code seems to update the overlays in a function called from
post-command-hook, but post-command-hook runs before redisplay updates
the window due to last command.  So you are using stale window-start
and window-end values, and if the last command scrolls some file names
into the view, those file names might not have overlays on them.

I think the preferred method is to use jit-lock-register to register
your function; see e.g. glasses.el for how this can be done.

> - When lines are wrapped around and line-move-visual is t, the mode can
>   make the line short enough so that it doesn't wrap anymore.  But
>   still next-line moves point to where it would belong if the mode were
>   not active, i.e., point jumps to somewhere on the same line.

That sounds like a bug, so a minimal recipe to reproduce this (in a
bug report, please ;-) would be appreciated.

Thanks.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-02 11:34 ` Eli Zaretskii
@ 2019-03-02 14:59   ` Tassilo Horn
  2019-03-03  9:46   ` Tassilo Horn
  1 sibling, 0 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-02 14:59 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

Hi Eli,

> The GNU Coding Style conventions frown on using "path" for anything
> but PATH-style directory lists.  I think you mean "file names" here.

Fine with me.

>> - Sometimes when scrolling fast and then stopping, only parts of the
>>   visible buffer portion got the overlays applied.  You can try
>>   triggering that problem by enabling the mode in a *grep* buffer and
>>   then scrolling a long way.
>
> Your code seems to update the overlays in a function called from
> post-command-hook, but post-command-hook runs before redisplay updates
> the window due to last command.  So you are using stale window-start
> and window-end values, and if the last command scrolls some file names
> into the view, those file names might not have overlays on them.
>
> I think the preferred method is to use jit-lock-register to register
> your function; see e.g. glasses.el for how this can be done.

Yes, that looks good.  I'll try that out.

>> - When lines are wrapped around and line-move-visual is t, the mode can
>>   make the line short enough so that it doesn't wrap anymore.  But
>>   still next-line moves point to where it would belong if the mode were
>>   not active, i.e., point jumps to somewhere on the same line.
>
> That sounds like a bug, so a minimal recipe to reproduce this (in a
> bug report, please ;-) would be appreciated.

Ok, I'll do that.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-02 11:05 [ELPA] New package proposal: visual-path-abbrev.el Tassilo Horn
  2019-03-02 11:34 ` Eli Zaretskii
@ 2019-03-02 21:25 ` Leo Liu
  2019-03-03  9:25   ` Tassilo Horn
  1 sibling, 1 reply; 23+ messages in thread
From: Leo Liu @ 2019-03-02 21:25 UTC (permalink / raw)
  To: emacs-devel

On 2019-03-02 12:05 +0100, Tassilo Horn wrote:
> I've just written a small minor mode which abbreviates file paths
> visually by using overlays.  When point enters such an overlay, the path
> is shown normally again.

I had the need to implement such a feature in ggtags.el almost since its
creation; see ggtags-abbreviate-files which is run by jit-lock.

Leo




^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-02 21:25 ` Leo Liu
@ 2019-03-03  9:25   ` Tassilo Horn
  2019-03-04  0:23     ` Leo Liu
  0 siblings, 1 reply; 23+ messages in thread
From: Tassilo Horn @ 2019-03-03  9:25 UTC (permalink / raw)
  To: Leo Liu; +Cc: emacs-devel

Leo Liu <sdl.web@gmail.com> writes:

> On 2019-03-02 12:05 +0100, Tassilo Horn wrote:
>> I've just written a small minor mode which abbreviates file paths
>> visually by using overlays.  When point enters such an overlay, the
>> path is shown normally again.
>
> I had the need to implement such a feature in ggtags.el almost since
> its creation; see ggtags-abbreviate-files which is run by jit-lock.

Oh, interesting, but probably a bit different use-case.  I want that the
file names stay editable and un-abbreviate as soon as point enters them
(at least as an option).  I think that's not possible when using the
'invisible text property.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-02 11:34 ` Eli Zaretskii
  2019-03-02 14:59   ` Tassilo Horn
@ 2019-03-03  9:46   ` Tassilo Horn
  2019-03-03 13:48     ` Stefan Monnier
  2019-03-03 15:11     ` Eli Zaretskii
  1 sibling, 2 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-03  9:46 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> Your code seems to update the overlays in a function called from
> post-command-hook, but post-command-hook runs before redisplay updates
> the window due to last command.  So you are using stale window-start
> and window-end values, and if the last command scrolls some file names
> into the view, those file names might not have overlays on them.
>
> I think the preferred method is to use jit-lock-register to register
> your function; see e.g. glasses.el for how this can be done.

Ok, here's a new version using that approach and basically it works.
However, there's a problem with the conditional 'display spec which
should in theory un-abbreviate the file name as soon as point enter's
the overlay's region.  Oftentimes that doesn't happen until I explicitly
force a redisplay with C-l or M-x.

Is there a good way to cope with that?

        Tassilo
        
--8<---------------cut here---------------start------------->8---
;;; visual-file-name-abbrev.el --- Visually abbreviate file names  -*- lexical-binding: t; -*-

;; Copyright (C) 2019 Free Software Foundation, Inc

;; Author: Tassilo Horn <tsdh@gnu.org>
;; Keywords: TODO
;; Version: TODO

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; This minor mode abbreviates the directory part of file names by using
;; overlays.  For example, a longish path like
;;
;;    /home/myuser/Documents/Letters/Personal-Family/Letter-to-John.tex
;;
;; will be displayed like this:
;;
;;   /h…/m…/D…/L…/P…-F…/Letter-to-John.tex
;;
;; By default, the abbreviate display is disabled when point enters the overlay
;; so that you can edit the file name normally.  Also, abbreviated file names
;; are only shown if the abbreviation as actually shorter as the original one
;; (which depends on what you add as replacement).
;;
;; There's stuff to customize, just check `M-x customize-group RET
;; visual-file-name-abbrev RET'.

;;; Code:

(require 'subr-x)
(require 'seq)

(defgroup visual-file-name-abbrev nil
  "Visually abbreviate the directory part of paths.")

(defcustom visual-file-name-abbrev-regex
  (concat "\\(?:file://\\)?/?"
	  "\\(?:[[:alnum:]@_.-]+/\\)+[[:alnum:]@_.-]*\\.\\w+")
  "Regexp matching paths.")

(defcustom visual-file-name-abbrev-replace-regex
  "[.@]?[[:alnum:]]\\([[:alnum:]]+\\)[-_/.]"
  "Regexp which will be visually replaced in paths.
All matches of this regexp's group number 1 in the paths matching
`visual-file-name-abbrev-regex' will be replaced by
`visual-file-name-abbrev-abbrev'.")

(defcustom visual-file-name-abbrev-abbrev "…"
  "String to be displayed instead of the match group 1 of
`visual-file-name-abbrev-regex'.")

(defun visual-file-name-abbrev--get-abbrev (path)
  (let ((file (file-name-nondirectory path))
	(dir (file-name-directory path)))
    (concat
     (file-name-as-directory
      (replace-regexp-in-string
       visual-file-name-abbrev-replace-regex
       visual-file-name-abbrev-abbrev dir nil nil 1))
     file)))

(defsubst visual-file-name-abbrev--get-overlay (pos)
  (car (seq-filter
	(lambda (o) (overlay-get o 'visual-file-name-abbrev))
	(overlays-at pos))))

(defun visual-file-name-abbrev--not-on-overlay-p (_buffer pos path abbrev)
  (when-let ((ol (visual-file-name-abbrev--get-overlay pos)))
    (or (< (point) (overlay-start ol))
	(> (point) (overlay-end ol)))))

(defun visual-file-name-abbrev--abbrev-shorter-p (_buffer _pos path abbrev)
  (< (string-width abbrev)
     (string-width path)))

(defvar visual-file-name-abbrev-display-predicates
  (list #'visual-file-name-abbrev--not-on-overlay-p
	#'visual-file-name-abbrev--abbrev-shorter-p))

(defun visual-file-name-abbrev--display-p (buffer pos path abbrev)
  (seq-every-p (lambda (pred)
		 (funcall pred buffer pos path abbrev))
	       visual-file-name-abbrev-display-predicates))

(defun visual-file-name-abbrev--delete-overlays (beg end)
  (dolist (ol (overlays-in beg end))
    (when (overlay-get ol 'visual-file-name-abbrev)
      (delete-overlay ol))))

(defun visual-file-name-abbrev--place-overlays (start end)
  (goto-char start)
  (while (re-search-forward visual-file-name-abbrev-regex end t)
    (let* ((m-beg (match-beginning 0))
	   (m-end (match-end 0))
	   (path (match-string 0))
	   (abbrev (visual-file-name-abbrev--get-abbrev path))
	   (ol (or (when-let ((o (visual-file-name-abbrev--get-overlay m-beg)))
		     (move-overlay o m-beg m-end)
		     o)
		   (make-overlay m-beg m-end nil t))))
      (overlay-put ol 'visual-file-name-abbrev t)
      (overlay-put ol 'evaporate t)
      (overlay-put ol 'help-echo path)
      (overlay-put
       ol 'display `(when (visual-file-name-abbrev--display-p
			   object buffer-position ,path ,abbrev)
		      . ,abbrev)))))

(defun visual-file-name-abbrev--jit-lock (beg end &optional _old-len)
  "Function registered for jit-lock."
  (let ((beg-line (save-excursion (goto-char beg) (line-beginning-position)))
	(end-line (save-excursion (goto-char end) (line-end-position))))
    (visual-file-name-abbrev--place-overlays beg-line end-line)))

(define-minor-mode visual-file-name-abbrev-mode
  "Visually abbreviate file paths."
  nil " VFNAbbr" nil
  (if visual-file-name-abbrev-mode
      (progn
	(jit-lock-register #'visual-file-name-abbrev--jit-lock)
	(visual-file-name-abbrev--jit-lock (window-start)
					   (window-end)))
    (jit-lock-unregister #'visual-file-name-abbrev--jit-lock)
    (visual-file-name-abbrev--delete-overlays 1 (1+ (buffer-size)))))
--8<---------------cut here---------------end--------------->8---



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03  9:46   ` Tassilo Horn
@ 2019-03-03 13:48     ` Stefan Monnier
  2019-03-03 15:11     ` Eli Zaretskii
  1 sibling, 0 replies; 23+ messages in thread
From: Stefan Monnier @ 2019-03-03 13:48 UTC (permalink / raw)
  To: emacs-devel

> (defun visual-file-name-abbrev--display-p (buffer pos path abbrev)
>   (seq-every-p (lambda (pred)
> 		 (funcall pred buffer pos path abbrev))
> 	       visual-file-name-abbrev-display-predicates))

I think this can be rewritten

    (defun visual-file-name-abbrev--display-p (buffer pos path abbrev)
      (run-hook-with-args-until-failure
       'visual-file-name-abbrev-display-predicates
       buffer pos filename abbrev))

which adds the ability to combine buffer-local and global parts.
I'd rename the var to use "-functions" as a suffix, to follow the usual
naming conventions.

BTW, why not pass the overlay as argument instead?

>       (overlay-put
>        ol 'display `(when (visual-file-name-abbrev--display-p
> 			   object buffer-position ,path ,abbrev)
> 		      . ,abbrev)))))

I understand you want the overlay to "auto-reveal" the text when point
enters it, but visual-file-name-abbrev--display-p seems "too dynamic" in
the sense that it recomputes a lot of things that could be computed
right here when building the overlay
(e.g. visual-file-name-abbrev--abbrev-shorter-p will always recompute
the same string-widths since the args it receives are always the same).


        Stefan




^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03  9:46   ` Tassilo Horn
  2019-03-03 13:48     ` Stefan Monnier
@ 2019-03-03 15:11     ` Eli Zaretskii
  2019-03-03 15:52       ` Tassilo Horn
  1 sibling, 1 reply; 23+ messages in thread
From: Eli Zaretskii @ 2019-03-03 15:11 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

> From: Tassilo Horn <tsdh@gnu.org>
> Date: Sun, 03 Mar 2019 10:46:09 +0100
> Cc: emacs-devel@gnu.org
> 
> However, there's a problem with the conditional 'display spec which
> should in theory un-abbreviate the file name as soon as point enter's
> the overlay's region.  Oftentimes that doesn't happen until I explicitly
> force a redisplay with C-l or M-x.
> 
> Is there a good way to cope with that?

Would it be possible for you to post a small test file that could be
used to show-case this package, and where you see this problem?  I'd
like to look into why that happens, and would appreciate a cooked test
case for doing this.

Thanks.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03 15:11     ` Eli Zaretskii
@ 2019-03-03 15:52       ` Tassilo Horn
  2019-03-03 17:18         ` Eli Zaretskii
  2019-03-04 18:03         ` Eli Zaretskii
  0 siblings, 2 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-03 15:52 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> However, there's a problem with the conditional 'display spec which
>> should in theory un-abbreviate the file name as soon as point enter's
>> the overlay's region.  Oftentimes that doesn't happen until I
>> explicitly force a redisplay with C-l or M-x.
>> 
>> Is there a good way to cope with that?
>
> Would it be possible for you to post a small test file that could be
> used to show-case this package, and where you see this problem?  I'd
> like to look into why that happens, and would appreciate a cooked test
> case for doing this.

You don't have to have a special file.  For me, this simple recipe did
the trick:

1. emacs -Q
2. M-x load-file RET visual-file-name-abbrev.el RET
3. M-x rgrep RET (defsubst RET *.el RET ~/Repos/el/emacs
4. In the *grep* buffer: M-x visual-file-name-abbrev-mode

As a result, the file names are indeed abbreviated but when I move up
and down using C-n/C-p, I'd expect the file name under point to be
displayed normally (so not the 'display spec but the normal text).  With
emacs -Q, the problem is even worse than with my usual config.  It seems
the conditional display spec is almost never tested, except when the
window is scrolled, but then usually after scrolling point is not on the
file name anymore which is now shown unabbreviated.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03 15:52       ` Tassilo Horn
@ 2019-03-03 17:18         ` Eli Zaretskii
  2019-03-03 17:55           ` Tassilo Horn
  2019-03-04 18:03         ` Eli Zaretskii
  1 sibling, 1 reply; 23+ messages in thread
From: Eli Zaretskii @ 2019-03-03 17:18 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

> From: Tassilo Horn <tsdh@gnu.org>
> Cc: emacs-devel@gnu.org
> Date: Sun, 03 Mar 2019 16:52:56 +0100
> 
> 1. emacs -Q
> 2. M-x load-file RET visual-file-name-abbrev.el RET
> 3. M-x rgrep RET (defsubst RET *.el RET ~/Repos/el/emacs
> 4. In the *grep* buffer: M-x visual-file-name-abbrev-mode
> 
> As a result, the file names are indeed abbreviated but when I move up
> and down using C-n/C-p, I'd expect the file name under point to be
> displayed normally (so not the 'display spec but the normal text).  With
> emacs -Q, the problem is even worse than with my usual config.  It seems
> the conditional display spec is almost never tested, except when the
> window is scrolled, but then usually after scrolling point is not on the
> file name anymore which is now shown unabbreviated.

OK, thanks.  I will look into this.

P.S. Did you use '(when CONDITION . SPEC)' display properties
elsewhere, or is this the first time for you?  I wonder whether this
display feature is used widely enough to be sufficiently tested.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03 17:18         ` Eli Zaretskii
@ 2019-03-03 17:55           ` Tassilo Horn
  0 siblings, 0 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-03 17:55 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel, Tassilo Horn

[-- Attachment #1: Type: text/html, Size: 123 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03  9:25   ` Tassilo Horn
@ 2019-03-04  0:23     ` Leo Liu
  0 siblings, 0 replies; 23+ messages in thread
From: Leo Liu @ 2019-03-04  0:23 UTC (permalink / raw)
  To: emacs-devel

On 2019-03-03 10:25 +0100, Tassilo Horn wrote:
> Oh, interesting, but probably a bit different use-case.  I want that the
> file names stay editable and un-abbreviate as soon as point enters them
> (at least as an option).  I think that's not possible when using the
> 'invisible text property.

Indeed different use cases. I was wanting to fit file names (such as
those found in java projects) to a certain width. So it stops
abbreviation as soon as the file name length is short enough. We then
rely on reveal-mode to toggle the abbreviated and full file names.




^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-03 15:52       ` Tassilo Horn
  2019-03-03 17:18         ` Eli Zaretskii
@ 2019-03-04 18:03         ` Eli Zaretskii
  2019-03-05 10:01           ` Tassilo Horn
  1 sibling, 1 reply; 23+ messages in thread
From: Eli Zaretskii @ 2019-03-04 18:03 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

> From: Tassilo Horn <tsdh@gnu.org>
> Cc: emacs-devel@gnu.org
> Date: Sun, 03 Mar 2019 16:52:56 +0100
> 
> 1. emacs -Q
> 2. M-x load-file RET visual-file-name-abbrev.el RET
> 3. M-x rgrep RET (defsubst RET *.el RET ~/Repos/el/emacs
> 4. In the *grep* buffer: M-x visual-file-name-abbrev-mode
> 
> As a result, the file names are indeed abbreviated but when I move up
> and down using C-n/C-p, I'd expect the file name under point to be
> displayed normally (so not the 'display spec but the normal text).  With
> emacs -Q, the problem is even worse than with my usual config.  It seems
> the conditional display spec is almost never tested, except when the
> window is scrolled, but then usually after scrolling point is not on the
> file name anymore which is now shown unabbreviated.

Yes, this feature is somewhat tricky to use.  The basic problem here
is that CONDITION is only evaluated when redisplay examines the text
on which you put this display spec, because only then the display
engine bumps into this spec.  And redisplay is extremely conservative
in what portions of text it examines, because doing so is generally
expensive, and makes Emacs less responsive.  So redisplay is full of
shortcuts and optimizations meant to avoid examining portions of text
where no changes are expected.

For your feature to work reliably, you need one or more overlays
examined even if the user just moves point, something that triggers a
heavily optimized version of redisplay (because moving point is a very
frequent operation).  You need to disable some of these optimizations.

One way of disabling those optimizations is to make some immaterial
change in one or more overlays, because overlay changes cause a more
thorough redisplay of the buffer.  You can, for example, change some
overlay property that will have no effect on display.

Another possibility is to have a buffer-local variable that you add to
the list of variables which trigger thorough redisplay of its buffer,
see the end of frame.el for how this is done.  Then whenever you want
redisplay to re-evaluate one or more of your overlays, you change the
value of that variable.

Both of those techniques will need to use post-command-hook, I think.

Caveat: I didn't try any of my suggestions, so I cannot be sure they
will work, although they should, of course.  (I did add the above
caveats to the ELisp manual, so they are now documented.)



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-04 18:03         ` Eli Zaretskii
@ 2019-03-05 10:01           ` Tassilo Horn
  2019-03-05 16:21             ` Eli Zaretskii
  2019-03-08  5:49             ` Stefan Monnier
  0 siblings, 2 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-05 10:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> For your feature to work reliably, you need one or more overlays
> examined even if the user just moves point, something that triggers a
> heavily optimized version of redisplay (because moving point is a very
> frequent operation).  You need to disable some of these optimizations.
>
> One way of disabling those optimizations is to make some immaterial
> change in one or more overlays, because overlay changes cause a more
> thorough redisplay of the buffer.  You can, for example, change some
> overlay property that will have no effect on display.
>
> Another possibility is to have a buffer-local variable that you add to
> the list of variables which trigger thorough redisplay of its buffer,
> see the end of frame.el for how this is done.  Then whenever you want
> redisplay to re-evaluate one or more of your overlays, you change the
> value of that variable.
>
> Both of those techniques will need to use post-command-hook, I think.
>
> Caveat: I didn't try any of my suggestions, so I cannot be sure they
> will work, although they should, of course.  (I did add the above
> caveats to the ELisp manual, so they are now documented.)

I'm now using option 1 and set the visual-file-name-abbrev overlay
property which I'm using to know which overlays are mine to (random)
instead of just t on the current and the last file name point was one in
a post-command-hook function.

That seems to do the trick although it's a bit sluggish when, e.g.,
pressing and holding C-n in a *grep* buffer in column 1 (which is
probably the worst case).

I haven't yet debugged what's the slow part but I guess it is the new
predicate `visual-file-name-abbrev--abbrev-visually-shorter-p' which
ensures that the abbreviation is only displayed if it is visually
shorter than the normal file name, i.e., it takes into account the
current font and the replacement ellipsis.  The standard one … is
twice as wide as a "normal" character on a non-terminal frame.

Other than that, do you think it's ok to add this package to ELPA?  If
so, is the (C) FSF and "This file is part of GNU Emacs" correct for an
ELPA(-only) package?

      Tassilo

--8<---------------cut here---------------start------------->8---
;;; visual-file-name-abbrev.el --- Visually abbreviate file names  -*- lexical-binding: t; -*-

;; Copyright (C) 2019 Free Software Foundation, Inc

;; Author: Tassilo Horn <tsdh@gnu.org>
;; Keywords: tools
;; Version: TODO

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; This minor mode abbreviates the directory part of file names by using
;; overlays.  For example, a longish file name like
;;
;;    /home/myuser/Documents/Letters/Personal-Family/Letter-to-John.tex
;;
;; will be displayed like this:
;;
;;   /h…/m…/D…/L…/P…-F…/Letter-to-John.tex
;;
;; By default, the abbreviate display is disabled when point enters the overlay
;; so that you can edit the file name normally.  Also, abbreviated file names
;; are only shown if the abbreviation as actually shorter as the original one
;; (which depends on what you add as replacement).
;;
;; There's stuff to customize, just check `M-x customize-group RET
;; visual-file-name-abbrev RET'.

;;; Code:

(require 'subr-x)
(require 'seq)

(defgroup visual-file-name-abbrev nil
  "Visually abbreviate the directory part of file names."
  :group 'tools)

(defcustom visual-file-name-abbrev-regex
  (concat "\\(?:file://\\)?/?"
	  "\\(?:[[:alnum:]@_.-]+/\\)+[[:alnum:]@_.-]*\\.\\w+")
  "Regexp matching file names."
  :group 'visual-file-name-abbrev
  :type 'regexp)

(defcustom visual-file-name-abbrev-replace-regex
  "[.@]?[[:alnum:]]\\([[:alnum:]]\\{2,\\}\\)[-_/.@]"
  "Regexp which will be visually replaced in file names.
All matches of this regexp's group number 1 in the file names
matching `visual-file-name-abbrev-regex' will be replaced by
`visual-file-name-abbrev-ellipsis'."
  :group 'visual-file-name-abbrev
  :type 'regexp)

(defcustom visual-file-name-abbrev-ellipsis "…"
  "String displayed instead of group 1 of `visual-file-name-abbrev-regex'."
  :group 'visual-file-name-abbrev
  :type 'string)

(defun visual-file-name-abbrev--get-abbrev (file-name)
  (let ((file (file-name-nondirectory file-name))
	(dir (file-name-directory file-name)))
    (concat
     (file-name-as-directory
      (replace-regexp-in-string
       visual-file-name-abbrev-replace-regex
       visual-file-name-abbrev-ellipsis dir nil nil 1))
     file)))

(defvar visual-file-name-abbrev--last-overlay nil)
(make-variable-buffer-local 'visual-file-name-abbrev--last-overlay)

(defsubst visual-file-name-abbrev--get-overlay (pos)
  (car (seq-filter
	(lambda (o) (overlay-get o 'visual-file-name-abbrev))
	(overlays-at pos))))

(defun visual-file-name-abbrev--post-command ()
  "Modifies the last and possibly current overlay to trigger their redisplay."
  (when visual-file-name-abbrev--last-overlay
    (overlay-put visual-file-name-abbrev--last-overlay 'visual-file-name-abbrev (random))
    (setq visual-file-name-abbrev--last-overlay nil))
  (when-let ((ol (visual-file-name-abbrev--get-overlay (point))))
    (overlay-put ol 'visual-file-name-abbrev (random))
    (setq visual-file-name-abbrev--last-overlay ol)))

(defun visual-file-name-abbrev--not-on-overlay-p (_buffer pos file-name abbrev)
  "Return non-nil if point is not inside the overlay at POS."
  (when-let ((ol (visual-file-name-abbrev--get-overlay pos)))
    (or (< (point) (overlay-start ol))
	(> (point) (overlay-end ol)))))

(defun visual-file-name-abbrev--abbrev-shorter-p (_buffer _pos file-name abbrev)
  "Return non-nil if ABBREV is shorter than FILE-NAME.
Shorter means less characters here."
  (< (string-width abbrev)
     (string-width file-name)))

(defsubst visual-file-name-abbrev--get-visual-width (str font)
  (with-current-buffer (get-buffer-create " *VFNAbbr work*")
    (setq buffer-undo-list t)
    (erase-buffer)
    (insert str)
    (seq-reduce (lambda (acc g) (+ acc (aref g 4)))
		(font-get-glyphs font (point-min) (point-max))
		0)))

(defun visual-file-name-abbrev--abbrev-visually-shorter-p (_buffer pos file-name abbrev)
  "Return non-nil if ABBREV's display representation is shorter than FILE-NAME.
This takes the font into account."
  (let ((font (font-at pos)))
    (< (visual-file-name-abbrev--get-visual-width abbrev font)
       (visual-file-name-abbrev--get-visual-width file-name font))))

(defcustom visual-file-name-abbrev-display-predicates
  (list #'visual-file-name-abbrev--not-on-overlay-p
	#'visual-file-name-abbrev--abbrev-visually-shorter-p)
  "A list of predicates inhibiting abbreviation of a file name.
A file name is only abbreviate if all predicates in this list
return true.

Each predicate is called with the following four arguments:

  - BUFFER: The buffer holding the abbreviation overlay.
  - POS: The position in BUFFER of the overlay.
  - FILE: The file name to be abbreviated.
  - ABBREV: The abbreviated version of the file name.

These predicates are available:

  - `visual-file-name-abbrev--not-on-overlay-p' ensures that an
    abbreviation is not shown when `point' in inside the overlays
    region.

  - `visual-file-name-abbrev--abbrev-shorter-p' ensures that an
    abbreviation is only shown if it is shorter (in the number of
    characters) than the original file name.

  - `visual-file-name-abbrev--abbrev-visually-shorter-p' ensures
    that an abbreviation is only shown if it is visually shorter
    than the original file name, i.e., it takes the current font
    and, e.g., double-width unicode characters into account."
  :group 'visual-file-name-abbrev
  :type '(repeat function))

(defun visual-file-name-abbrev--display-p (buffer pos file-name abbrev)
  (seq-every-p (lambda (pred)
		 (funcall pred buffer pos file-name abbrev))
	       visual-file-name-abbrev-display-predicates))

(defun visual-file-name-abbrev--delete-overlays (beg end)
  (dolist (ol (overlays-in beg end))
    (when (overlay-get ol 'visual-file-name-abbrev)
      (delete-overlay ol))))

(defun visual-file-name-abbrev--place-overlays (start end)
  (goto-char start)
  (while (re-search-forward visual-file-name-abbrev-regex end t)
    (let* ((m-beg (match-beginning 0))
	   (m-end (match-end 0))
	   (file-name (match-string 0))
	   (abbrev (visual-file-name-abbrev--get-abbrev file-name))
	   (ol (or (when-let ((o (visual-file-name-abbrev--get-overlay m-beg)))
		     (move-overlay o m-beg m-end)
		     o)
		   (make-overlay m-beg m-end nil t))))
      (overlay-put ol 'visual-file-name-abbrev t)
      (overlay-put ol 'evaporate t)
      (overlay-put ol 'help-echo file-name)
      (overlay-put
       ol 'display `(when (visual-file-name-abbrev--display-p
			   object buffer-position ,file-name ,abbrev)
		      . ,abbrev)))))

(defun visual-file-name-abbrev--jit-lock (beg end &optional _old-len)
  "Function registered for jit-lock."
  (let ((beg-line (save-excursion (goto-char beg) (line-beginning-position)))
	(end-line (save-excursion (goto-char end) (line-end-position))))
    (visual-file-name-abbrev--place-overlays beg-line end-line)))

;;###autoload
(define-minor-mode visual-file-name-abbrev-mode
  "Visually abbreviate the directory part of file names."
  nil " VFNAbbr" nil
  (if visual-file-name-abbrev-mode
      (progn
	(jit-lock-register #'visual-file-name-abbrev--jit-lock)
	(add-hook 'post-command-hook #'visual-file-name-abbrev--post-command nil t)
	(visual-file-name-abbrev--jit-lock (window-start)
					   (window-end)))
    (jit-lock-unregister #'visual-file-name-abbrev--jit-lock)
    (remove-hook 'post-command-hook #'visual-file-name-abbrev--post-command t)
    (visual-file-name-abbrev--delete-overlays 1 (1+ (buffer-size)))))

(provide 'visual-file-name-abbrev)

;;; visual-file-name-abbrev.el ends here
--8<---------------cut here---------------end--------------->8---



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-05 10:01           ` Tassilo Horn
@ 2019-03-05 16:21             ` Eli Zaretskii
  2019-03-05 18:32               ` Tassilo Horn
  2019-03-08  5:49             ` Stefan Monnier
  1 sibling, 1 reply; 23+ messages in thread
From: Eli Zaretskii @ 2019-03-05 16:21 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

> From: Tassilo Horn <tsdh@gnu.org>
> Cc: emacs-devel@gnu.org
> Date: Tue, 05 Mar 2019 11:01:04 +0100
> 
> > Caveat: I didn't try any of my suggestions, so I cannot be sure they
> > will work, although they should, of course.  (I did add the above
> > caveats to the ELisp manual, so they are now documented.)
> 
> I'm now using option 1 and set the visual-file-name-abbrev overlay
> property which I'm using to know which overlays are mine to (random)
> instead of just t on the current and the last file name point was one in
> a post-command-hook function.
> 
> That seems to do the trick

I'm glad it worked for you.

> although it's a bit sluggish when, e.g., pressing and holding C-n in
> a *grep* buffer in column 1 (which is probably the worst case).
> 
> I haven't yet debugged what's the slow part but I guess it is the new
> predicate `visual-file-name-abbrev--abbrev-visually-shorter-p' which
> ensures that the abbreviation is only displayed if it is visually
> shorter than the normal file name, i.e., it takes into account the
> current font and the replacement ellipsis.  The standard one … is
> twice as wide as a "normal" character on a non-terminal frame.

It is, of course, best to make functions on post-command-hook as
speedy as possible.  But don't be surprised if you find out that is
not the main reason for the sluggishness: you've just disabled a very
important redisplay optimization, so you should expect some hit on the
responsiveness front.  These optimizations are there for a reason,
even in these days of super-fast CPUs.

> Other than that, do you think it's ok to add this package to ELPA?  If
> so, is the (C) FSF and "This file is part of GNU Emacs" correct for an
> ELPA(-only) package?

I'll let Stefan answer this.  And I believe we have recently started
asking contributors to say explicitly in email or in the file that
they contribute the package to Emacs, for legal purposes.  So please
do.

Thanks.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-05 16:21             ` Eli Zaretskii
@ 2019-03-05 18:32               ` Tassilo Horn
  0 siblings, 0 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-05 18:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> although it's a bit sluggish when, e.g., pressing and holding C-n in
>> a *grep* buffer in column 1 (which is probably the worst case).
>> 
>> I haven't yet debugged what's the slow part but I guess it is the new
>> predicate `visual-file-name-abbrev--abbrev-visually-shorter-p' which
>> ensures that the abbreviation is only displayed if it is visually
>> shorter than the normal file name, i.e., it takes into account the
>> current font and the replacement ellipsis.  The standard one … is
>> twice as wide as a "normal" character on a non-terminal frame.
>
> It is, of course, best to make functions on post-command-hook as
> speedy as possible.  But don't be surprised if you find out that is
> not the main reason for the sluggishness: you've just disabled a very
> important redisplay optimization, so you should expect some hit on the
> responsiveness front.  These optimizations are there for a reason,
> even in these days of super-fast CPUs.

Right.  In my case, I just need that for the optional (but
default-enabled) "display normally if point is on the overlay" feature.
So the post-command-hook function could check if it is enabled and do
nothing if it's not.  Or it could even remove itself from p-c-h.  (But
then, it wouldn't start working in that buffer if the feature was later
enabled (which is just adding a function to a list)).

>> Other than that, do you think it's ok to add this package to ELPA?  If
>> so, is the (C) FSF and "This file is part of GNU Emacs" correct for an
>> ELPA(-only) package?
>
> I'll let Stefan answer this.  And I believe we have recently started
> asking contributors to say explicitly in email or in the file that
> they contribute the package to Emacs, for legal purposes.  So please
> do.

Ok, great.  Then let's hear what he has to say.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-05 10:01           ` Tassilo Horn
  2019-03-05 16:21             ` Eli Zaretskii
@ 2019-03-08  5:49             ` Stefan Monnier
  2019-03-08 14:02               ` Tassilo Horn
  1 sibling, 1 reply; 23+ messages in thread
From: Stefan Monnier @ 2019-03-08  5:49 UTC (permalink / raw)
  To: emacs-devel

> I'm now using option 1 and set the visual-file-name-abbrev overlay
> property which I'm using to know which overlays are mine to (random)
> instead of just t on the current and the last file name point was one in
> a post-command-hook function.

Another option might be to use cursor-sensor-mode to open/close
those abbreviations.

> Other than that, do you think it's ok to add this package to ELPA?

Yes, tho please try and change the naming to follow GNU's convention
w.r.t to "path vs filename".

> If so, is the (C) FSF and "This file is part of GNU Emacs" correct for
> an ELPA(-only) package?

Yes, thank you.


        Stefan




^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-08  5:49             ` Stefan Monnier
@ 2019-03-08 14:02               ` Tassilo Horn
  2019-03-08 17:34                 ` Tassilo Horn
  2019-03-08 18:52                 ` Stefan Monnier
  0 siblings, 2 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-08 14:02 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> I'm now using option 1 and set the visual-file-name-abbrev overlay
>> property which I'm using to know which overlays are mine to (random)
>> instead of just t on the current and the last file name point was one
>> in a post-command-hook function.
>
> Another option might be to use cursor-sensor-mode to open/close those
> abbreviations.

Hm, that also sounds good.  Is cursor-sensor-functions only a text
property or can I also add that to my overlay?

I guess I could change my code so that only those file names get an
overlay where all predicates are satisfied (abbrev shorter or visually
shorter than file name), and handle the uncollapsing using
cursor-sensor-mode.  Then I would't need a conditional display spec at
all.

How'd I do the uncollapsing in my cursor-sensor-function?  Delete the
overlay on 'entered and add it again on 'left?

>> Other than that, do you think it's ok to add this package to ELPA?
>
> Yes, tho please try and change the naming to follow GNU's convention
> w.r.t to "path vs filename".

Yes, I've already changed it to speak of filenames everywhere.

>> If so, is the (C) FSF and "This file is part of GNU Emacs" correct for
>> an ELPA(-only) package?
>
> Yes, thank you.

Ok, great.  Then I'll commit it as soon as I've tried out
cursor-sensor-mode and decided if that's a better approach.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-08 14:02               ` Tassilo Horn
@ 2019-03-08 17:34                 ` Tassilo Horn
  2019-03-08 19:01                   ` Stefan Monnier
  2019-03-08 18:52                 ` Stefan Monnier
  1 sibling, 1 reply; 23+ messages in thread
From: Tassilo Horn @ 2019-03-08 17:34 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Tassilo Horn <tsdh@gnu.org> writes:

Hi Stefan,

>> Another option might be to use cursor-sensor-mode to open/close those
>> abbreviations.
>
> Hm, that also sounds good.  Is cursor-sensor-functions only a text
> property or can I also add that to my overlay?

Works also with overlays.  Perfect!

> I guess I could change my code so that only those file names get an
> overlay where all predicates are satisfied (abbrev shorter or visually
> shorter than file name), and handle the uncollapsing using
> cursor-sensor-mode.  Then I would't need a conditional display spec at
> all.
>
> How'd I do the uncollapsing in my cursor-sensor-function?  Delete the
> overlay on 'entered and add it again on 'left?

What I do now is swapping the 'display property value to a custom
property on 'entered and moving it back on 'left.

That's really much, much better than before, so thanks a lot for the
pointer to `cursor-sensor-mode'!

Ok, now after the hymn of praise, here's the caveat which I couldn't
solve so far: When point leaves one of my overlays and immediately
appears in another one, the `cursor-sensor-functions' are NOT CALLED.
Of course, I expected to get a one call with 'left followed by a call
with 'entered.

Can we consider that a bug in cursor-sensor or is that the expected
behavior?  And more importantly, can I influence it so that it works for
my use-case?

An easy recipe for reproduction is to run M-x rgrep, then activate my
mode in the *grep* buffer, and then move up and down using C-p / C-n.

     Tassilo
     
--8<---------------cut here---------------start------------->8---
;;; visual-filename-abbrev.el --- Visually abbreviate filenames  -*- lexical-binding: t; -*-

;; Copyright (C) 2019 Free Software Foundation, Inc

;; Author: Tassilo Horn <tsdh@gnu.org>
;; Keywords: tools
;; Version: TODO

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; This minor mode abbreviates the directory part of file names by using
;; overlays.  For example, a longish file name like
;;
;;    /home/myuser/Documents/Letters/Personal-Family/Letter-to-John.tex
;;
;; will be displayed like this:
;;
;;   /h…/m…/D…/L…/P…-F…/Letter-to-John.tex
;;
;; By default, the abbreviate display is disabled when point enters the overlay
;; so that you can edit the file name normally.  Also, abbreviated file names
;; are only shown if the abbreviation as actually shorter as the original one
;; (which depends on what you add as replacement).
;;
;; There's stuff to customize, just check `M-x customize-group RET
;; visual-filename-abbrev RET'.

;;; Code:

(require 'subr-x)
(require 'seq)

(defgroup visual-filename-abbrev nil
  "Visually abbreviate the directory part of file names."
  :group 'tools)

(defcustom visual-filename-abbrev-regex
  (concat "\\(?:file://\\)?/?"
	  "\\(?:[[:alnum:]@_.-]+/\\)+[[:alnum:]@_.-]*\\.\\w+")
  "Regexp matching file names."
  :group 'visual-filename-abbrev
  :type 'regexp)

(defcustom visual-filename-abbrev-replace-regex
  "[.@]?[[:alnum:]]\\([[:alnum:]]\\{2,\\}\\)[-_/.@]"
  "Regexp which will be visually replaced in file names.
All matches of this regexp's group number 1 in the file names
matching `visual-filename-abbrev-regex' will be replaced by
`visual-filename-abbrev-ellipsis'."
  :group 'visual-filename-abbrev
  :type 'regexp)

(defcustom visual-filename-abbrev-ellipsis "…"
  "String displayed instead of group 1 of `visual-filename-abbrev-regex'."
  :group 'visual-filename-abbrev
  :type 'string)

(defcustom visual-filename-abbrev-unabbreviate-under-point t
  "If non-nil, filenames under point are displayed unabbreviated."
  :group 'visual-filename-abbrev
  :type 'boolean)

(defun visual-filename-abbrev--get-abbrev (filename)
  (let ((file (file-name-nondirectory filename))
	(dir (file-name-directory filename)))
    (concat
     (file-name-as-directory
      (replace-regexp-in-string
       visual-filename-abbrev-replace-regex
       visual-filename-abbrev-ellipsis dir nil nil 1))
     file)))

(defsubst visual-filename-abbrev--get-overlay (pos)
  (car (seq-filter
	(lambda (o) (overlay-get o 'visual-filename-abbrev))
	(overlays-at pos))))

(defun visual-filename-abbrev--abbrev-shorter-p (_buffer _pos filename abbrev)
  "Return non-nil if ABBREV is shorter than FILENAME.
Shorter means less characters here."
  (< (string-width abbrev)
     (string-width filename)))

(defsubst visual-filename-abbrev--get-visual-width (str font)
  (seq-reduce (lambda (acc g) (+ acc (aref g 4)))
	      (font-get-glyphs font 0 (length str) str)
	      0))

(defun visual-filename-abbrev--abbrev-visually-shorter-p (buffer pos filename abbrev)
  "Return non-nil if ABBREV's display representation is shorter than FILENAME.
This takes the font into account."
  ;; NOTE: The docs say that object in an conditional display spec is always a
  ;; buffer, but actually it sometimes is a window.  See bug#34771.
  (let ((font (font-at pos (if (windowp buffer)
			       buffer
			     (get-buffer-window buffer)))))
    (< (visual-filename-abbrev--get-visual-width abbrev font)
       (visual-filename-abbrev--get-visual-width filename font))))

(defcustom visual-filename-abbrev-predicates
  (list #'visual-filename-abbrev--abbrev-visually-shorter-p)
  "A list of predicates inhibiting abbreviation of a file name.
A file name is only abbreviate if all predicates in this list
return true.

Each predicate is called with the following four arguments:

  - BUFFER: The buffer holding the abbreviation overlay.
  - POS: The position in BUFFER of the overlay.
  - FILE: The file name to be abbreviated.
  - ABBREV: The abbreviated version of the file name.

These predicates are available:

  - `visual-filename-abbrev--abbrev-shorter-p' ensures that an
    abbreviation is only shown if it is shorter (in the number of
    characters) than the original file name.  This is fast but
    doesn't work too good if `visual-filename-abbrev-ellipsis' is
    displayed wider than what's abbreviater (which depends on the
    font).

  - `visual-filename-abbrev--abbrev-visually-shorter-p' ensures
    that an abbreviation is only shown if it is visually shorter
    than the original file name, i.e., it takes the current font
    and, e.g., double-width unicode characters into account.
    This predicate is a bit more expensive to compute."
  :group 'visual-filename-abbrev
  :type '(repeat function))

(defun visual-filename-abbrev--abbreviate-p (buffer pos filename abbrev)
  (seq-every-p (lambda (pred)
		 (funcall pred buffer pos filename abbrev))
	       visual-filename-abbrev-predicates))

(defun visual-filename-abbrev--delete-overlays (beg end)
  (dolist (ol (overlays-in beg end))
    (when (overlay-get ol 'visual-filename-abbrev)
      (delete-overlay ol))))

(defun visual-filename-abbrev--cursor-sensor (window old-pos dir)
  (message "cs: %S %S %S" window old-pos dir)
  (when-let ((ol (visual-filename-abbrev--get-overlay
		  (if (eq dir 'entered)
		      (point)
		    ;; 1- because if we leave the overlay to the right,
		    ;; old-pos is one more that the overlay's end.
		    (if (> point old-pos)
			(1- old-pos)
		      (1+ old-pos))))))
    (message "  => %S" ol)
    (if (eq dir 'entered)
	(when-let ((d (overlay-get ol 'display)))
	  (overlay-put ol 'visual-filename-abbrev--display-backup d)
	  (overlay-put ol 'display nil))
      (when-let ((d (overlay-get ol 'visual-filename-abbrev--display-backup)))
	(overlay-put ol 'display d)
	(overlay-put ol 'visual-filename-abbrev--display-backup nil)))))

(defun visual-filename-abbrev--place-overlays (start end)
  (goto-char start)
  (while (re-search-forward visual-filename-abbrev-regex end t)
    (let* ((m-beg (match-beginning 0))
	   (m-end (match-end 0))
	   (filename (match-string 0))
	   (abbrev (visual-filename-abbrev--get-abbrev filename)))
      (when (visual-filename-abbrev--abbreviate-p
	     (current-buffer) (point) filename abbrev)
	(let ((ol (or (when-let ((o (visual-filename-abbrev--get-overlay m-beg)))
			(move-overlay o m-beg m-end)
			o)
		      (make-overlay m-beg m-end nil t))))
	  (when visual-filename-abbrev-unabbreviate-under-point
	    (overlay-put ol 'cursor-sensor-functions
			 (list #'visual-filename-abbrev--cursor-sensor)))
	  (overlay-put ol 'visual-filename-abbrev t)
	  (overlay-put ol 'evaporate t)
	  (overlay-put ol 'help-echo filename)
	  (overlay-put ol 'display abbrev))))))

(defun visual-filename-abbrev--jit-lock (beg end &optional _old-len)
  "Function registered for jit-lock."
  (let ((beg-line (save-excursion (goto-char beg) (line-beginning-position)))
	(end-line (save-excursion (goto-char end) (line-end-position))))
    (visual-filename-abbrev--place-overlays beg-line end-line)))

(defvar visual-filename-abbrev--csm-before-activation nil)
(make-variable-buffer-local 'visual-filename-abbrev--csm-before-activation)

;;###autoload
(define-minor-mode visual-filename-abbrev-mode
  "Visually abbreviate the directory part of file names."
  nil " VFAbbr" nil
  (if visual-filename-abbrev-mode
      (progn
	(jit-lock-register #'visual-filename-abbrev--jit-lock)
	(require 'cursor-sensor)
	;; Remember if c-s-m has been enabled before we enable it.
	(setq visual-filename-abbrev--csm-before-activation cursor-sensor-mode)
	(cursor-sensor-mode)
	(visual-filename-abbrev--jit-lock (window-start)
					  (window-end)))
    (jit-lock-unregister #'visual-filename-abbrev--jit-lock)
    ;; Deactivate it only if it has been disabled before we started it.
    (when visual-filename-abbrev--csm-before-activation
      (cursor-sensor-mode -1))
    (visual-filename-abbrev--delete-overlays 1 (1+ (buffer-size)))))

(provide 'visual-filename-abbrev)

;; Local Variables:
;; bug-reference-url-format: "https://debbugs.gnu.org/cgi/bugreport.cgi?bug=%s"
;; End:

;;; visual-filename-abbrev.el ends here
--8<---------------cut here---------------end--------------->8---




^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-08 14:02               ` Tassilo Horn
  2019-03-08 17:34                 ` Tassilo Horn
@ 2019-03-08 18:52                 ` Stefan Monnier
  1 sibling, 0 replies; 23+ messages in thread
From: Stefan Monnier @ 2019-03-08 18:52 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

>> Another option might be to use cursor-sensor-mode to open/close those
>> abbreviations.
> Hm, that also sounds good.  Is cursor-sensor-functions only a text
> property or can I also add that to my overlay?

Either way should work.

Note that your current method has the advantage that it (presumably)
automatically works "right" when the buffer is displayed in multiple
windows (where the "reveled" part in one window isn't revealed in the
other).  Doing the same by activating/deactivating overlays is
more painful.

> How'd I do the uncollapsing in my cursor-sensor-function?  Delete the
> overlay on 'entered and add it again on 'left?

I'd just move its `display` property to another property and then back
(I'd keep the overlay to detect when you leave).

> Ok, great.  Then I'll commit it as soon as I've tried out
> cursor-sensor-mode and decided if that's a better approach.

I see you committed something but haven't had time to look at it yet.
Thanks,


        Stefan



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-08 17:34                 ` Tassilo Horn
@ 2019-03-08 19:01                   ` Stefan Monnier
  2019-03-08 22:18                     ` Stefan Monnier
  0 siblings, 1 reply; 23+ messages in thread
From: Stefan Monnier @ 2019-03-08 19:01 UTC (permalink / raw)
  To: emacs-devel

> Ok, now after the hymn of praise, here's the caveat which I couldn't
> solve so far: When point leaves one of my overlays and immediately
> appears in another one, the `cursor-sensor-functions' are NOT CALLED.
> Of course, I expected to get a one call with 'left followed by a call
> with 'entered.

IIRC you should indeed get 2 calls (one for leave and one for enter),
unless the overlays are adjacent I think (the code looks at
char-properties so doesn't pay much attention to overlay boundaries
themselves).

> Can we consider that a bug in cursor-sensor or is that the expected
> behavior?  And more importantly, can I influence it so that it works for
> my use-case?

Not sure if it's a bug, but if the problem is that your overlays are
adjacent (so that all the chars between the old and the new positions
have the same `cursor-sensor-functions` property), then you can fix it
in one of two ways:
- change your overlay's boundaries so as to leave some chars between
  the overlays.
- use artificially non-eql values of the functions placed on
  `cursor-sensor-functions` (e.g. using closures that close over the
  overlay itself).

> An easy recipe for reproduction is to run M-x rgrep, then activate my
> mode in the *grep* buffer, and then move up and down using C-p / C-n.

Haven't tried it yet, but it sounds like maybe it's just a plain bug in
cursor-sensor.


        Stefan



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-08 19:01                   ` Stefan Monnier
@ 2019-03-08 22:18                     ` Stefan Monnier
  2019-03-09  6:52                       ` Tassilo Horn
  0 siblings, 1 reply; 23+ messages in thread
From: Stefan Monnier @ 2019-03-08 22:18 UTC (permalink / raw)
  To: emacs-devel

> Haven't tried it yet, but it sounds like maybe it's just a plain bug in
> cursor-sensor.

Indeed, it seems the patch below fixes it (IOW, the problem was that
the code handled text-properties correctly but not overlay properties).


        Stefan


diff --git a/lisp/emacs-lisp/cursor-sensor.el b/lisp/emacs-lisp/cursor-sensor.el
index a21d78998a..66b940f7fb 100644
--- a/lisp/emacs-lisp/cursor-sensor.el
+++ b/lisp/emacs-lisp/cursor-sensor.el
@@ -160,7 +160,7 @@ cursor-sensor--detect
         (setcdr old nil))
       (if (or (and (null new) (null (cdr old)))
               (and (eq new (cdr old))
-                   (eq (next-single-property-change
+                   (eq (next-single-char-property-change
                         start 'cursor-sensor-functions nil end)
                        end)))
           ;; Clearly nothing to do.
@@ -172,7 +172,7 @@ cursor-sensor--detect
                   (let ((pos start)
                         (missing nil))
                     (while (< pos end)
-                      (setq pos (next-single-property-change
+                      (setq pos (next-single-char-property-change
                                  pos 'cursor-sensor-functions
                                  nil end))
                       (unless (memq f (get-char-property



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [ELPA] New package proposal: visual-path-abbrev.el
  2019-03-08 22:18                     ` Stefan Monnier
@ 2019-03-09  6:52                       ` Tassilo Horn
  0 siblings, 0 replies; 23+ messages in thread
From: Tassilo Horn @ 2019-03-09  6:52 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Stefan Monnier <monnier@IRO.UMontreal.CA> writes:

>> Haven't tried it yet, but it sounds like maybe it's just a plain bug
>> in cursor-sensor.
>
> Indeed, it seems the patch below fixes it (IOW, the problem was that
> the code handled text-properties correctly but not overlay
> properties).

I confirm it works as expected.  Thanks a lot!

        Tassilo



^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2019-03-09  6:52 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-03-02 11:05 [ELPA] New package proposal: visual-path-abbrev.el Tassilo Horn
2019-03-02 11:34 ` Eli Zaretskii
2019-03-02 14:59   ` Tassilo Horn
2019-03-03  9:46   ` Tassilo Horn
2019-03-03 13:48     ` Stefan Monnier
2019-03-03 15:11     ` Eli Zaretskii
2019-03-03 15:52       ` Tassilo Horn
2019-03-03 17:18         ` Eli Zaretskii
2019-03-03 17:55           ` Tassilo Horn
2019-03-04 18:03         ` Eli Zaretskii
2019-03-05 10:01           ` Tassilo Horn
2019-03-05 16:21             ` Eli Zaretskii
2019-03-05 18:32               ` Tassilo Horn
2019-03-08  5:49             ` Stefan Monnier
2019-03-08 14:02               ` Tassilo Horn
2019-03-08 17:34                 ` Tassilo Horn
2019-03-08 19:01                   ` Stefan Monnier
2019-03-08 22:18                     ` Stefan Monnier
2019-03-09  6:52                       ` Tassilo Horn
2019-03-08 18:52                 ` Stefan Monnier
2019-03-02 21:25 ` Leo Liu
2019-03-03  9:25   ` Tassilo Horn
2019-03-04  0:23     ` Leo Liu

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).