Subject: Re: bug#78250: 31.0.50; Eglot: eglot-show-call-hierarchy

From: João Távora <[email-masked]>
Date: Thu, 08 May 2025 12:54:19 +0100

Eli Zaretskii <[email-masked]> writes:

>> From: Gerd Möllmann <[email-masked]>

Hi Gerd,

>> 1. Wishlist: Expanding/collapsing nodes and jumping to call sites is not
>> easily reachable with the "usual" keys like TAG, RET, and so on that are
>> used by the profiler, Magit etc. It would be very nice if that could be
>> added.

As far as I can see, TAB (I think you meant this) already invokes
widget-forward when point is on a "widget".  It invokes button-forward
when point is on a button.  So while there are no bindings in
eglot-hierarchy-mode-map, you should be able to use those keys.  Also
afaics, RET "does what you mean": it expands a node or or brings you to
the locus of a call, depending where you hit it.

Anyway, patches welcome.  However, I would favor the development of
tree-widget-mode (or widget mode) where these bindings and UI is in
effect.  Eglot is already using tree-widget.el.

Alternatively, if you realy want to improve things, patches welcome to
completely remove the dependency on tree-widget.el and replace it with a
dependency on a new graph-representation library.  Perhaps you could
lift the bespoke one that profiler.el uses into a new library?  Call it
hierarchy2.el.  Then make a :core ELPA package out of that so that
call/type hierarchies remain functional for ELPA-downloaded Eglot and
backward compatible to Emacs 26/27/28/29/30 (maybe 26 could be ditched,
I guess).

Just a note that I've experimented with hierarchy.el (which uses
tree-widget.el) and found it brings no benefit at all over directly
calling tree-widget.el. 

>> 2. Bug: Clicking on "chars_in_text" in the tree above takes me here:

Thanks, this is definitely a bug.  Pressing a button for a call site
should take you to the site, not the definition of caller/callee.  There
was a thinko here which made it only work for defininitions within the
same LSP document.

Note that there could be a separate binding to go to the definition of
the caller/callee, as that information is also readily available.
Perhaps Eglot could have an additional (small)
eglot-hierarchy-xref-backend so that the normal M-. does what the user
would expect from it (go to definition at point). Patches welcome here, too.

Anyway, please try the patch after my sig.  It fixes the mis-navigations
you reported.

João

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index d33b0b05fd4..e666389596d 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -4601,7 +4601,7 @@ eglot-hierarchy-label-map
     map)
   "Keymap active in labels Eglot hierarchy buffers.")
 
-(defun eglot--hierarchy-label (node)
+(defun eglot--hierarchy-label (node parent-uri)
   (eglot--dbind ((HierarchyItem) name uri _detail ((:range item-range))) node
     (with-temp-buffer
       (insert (propertize
@@ -4617,7 +4617,7 @@ eglot--hierarchy-label
        'keymap eglot-hierarchy-label-map
        'action
        (lambda (_btn)
-         (pop-to-buffer (find-file-noselect (eglot-uri-to-path uri)))
+         (pop-to-buffer (find-file-noselect (eglot-uri-to-path (or parent-uri uri))))
          (eglot--goto
           (or
            (elt
@@ -4657,12 +4657,13 @@ eglot--hierarchy-2
   (cl-labels ((expander-for (node)
                 (lambda (_widget)
                   (mapcar
-                   #'convert
+                   (lambda (n)
+                     (convert n (plist-get node :uri)))
                    (eglot--hierarchy-children node))))
-              (convert (node)
+              (convert (node parent-uri)
                 (let ((w (widget-convert
                           'tree-widget
-                          :tag (eglot--hierarchy-label node)
+                          :tag (eglot--hierarchy-label node parent-uri)
                           :expander (expander-for node))))
                   (widget-put w :empty-icon
                               (widget-get w :leaf-icon))
@@ -4670,7 +4671,7 @@ eglot--hierarchy-2
     (let ((inhibit-read-only t))
       (erase-buffer)
       (mapc (lambda (r)
-              (let ((w (widget-create (convert r))))
+              (let ((w (widget-create (convert r nil))))
                 (widget-apply-action w)))
             eglot--hierarchy-roots)
       (goto-char (point-min))))