Ponte sobre un método y mira sus usos sin moverte: el "Find Usages" con preview que llevaba años instalado


14 de junio de 2026

La escena que echaba de menos

Estás leyendo una clase. Te paras sobre un método —pongamos un ~getEmail(): string~— y el IDE te susurra: "7 usages". Picas, y se abre un panelito con los siete sitios donde se llama, y a un lado el código real de cada uno. Te mueves por la lista, ves el contexto sin saltar, encuentras el que buscabas, Enter, y ya estás allí. Si no era ninguno, cierras y sigues donde estabas.

Lo usas cien veces al día sin pensar. Y al pasarme a Doom lo busqué, no lo encontré a la primera, y pensé: "esto sí que lo voy a perder". Spoiler: no lo perdí. Estaba ahí. Apagado.

Tres sabores de "¿dónde se usa esto?"

Doom, con su módulo de LSP, trae varias formas de ver los usos de un símbolo. Yo no conocía ninguna:

  1. Lista en ventana apartegD (g mayúscula). Todos los usos listados en una ventana. Útil cuando hay muchos y quieres la lista completa para ir tachando.

  2. Peek con preview — el clavado a IntelliJ. Un popup pegado al cursor: a un lado la lista, al otro la vista previa del código de cada uso. Te mueves con j / k, el preview cambia en vivo, saltas solo si quieres. Es lsp-ui-peek-find-references.

  3. Árbol lateral — una ventana de treemacs a un lado, redimensionable, con las rutas completas y plegable. Es lsp-treemacs-references. Ideal cuando hay treinta referencias, o cuando estás en zen-mode (que estrecha la ventana y ahoga al peek: el árbol, al ser ventana aparte, ni se entera).

Los tres venían ya instalados con el módulo de LSP de Doom. Lo único que les faltaba era una tecla.

Por qué no tenían tecla, y dónde se la puse

El atajo natural para "references" sería gr ("go references"), pero en Doom gr ya evalúa la región de código (+eval:region). En un fichero Lisp tiene todo el sentido —marcas un trozo y lo ejecutas—. En PHP, no: ahí gr no hace nada útil. Así que lo reutilizamos solo cuando hay LSP:

;; gr -> peek (rapido) ; gR -> arbol treemacs (listas grandes / zen)
;; En buffers sin LSP, gr/gR siguen siendo el eval de siempre.
(defun my/gr-references-dwim ()
  (interactive)
  (if (bound-and-true-p lsp-mode)
      (lsp-ui-peek-find-references)
    (call-interactively #'+eval:region)))

Un detalle que me costó una tarde: Doom bindea gr en un keymap de alta prioridad (general "override"), así que un binding en lsp-mode-map quedaba tapado aunque estuviera bien puesto. Y para más coña, la función con la que yo lo verificaba (key-binding) me mentía desde fuera de Emacs. La verdad solo apareció listando el orden real de keymaps activos con current-active-maps. Lección: si tu herramienta de diagnóstico te da una respuesta rara dos veces, sospecha de la herramienta.

El GAP de verdad: los métodos mágicos

Aquí viene lo gordo, y donde por primera vez en toda la serie tuve que admitir que PhpStorm gana.

PHP tiene métodos mágicos. El rey hoy es __invoke: hace que un objeto se pueda llamar como si fuera una función. Se usa a punta pala —command handlers, single-action controllers, invokables de todo tipo:

final class SendWelcomeEmail
{
    public function __invoke(User $user): void { /* ... */ }
}

// y en otro sitio, se dispara invocando el objeto:
($sendWelcomeEmail)($user);   // <- esto ejecuta __invoke

Pon el cursor sobre ese __invoke, pide "find references"… y salen cero. ¿Por qué? Porque nadie escribe ->__invoke(). Se invoca implícitamente (($obj)($arg)), y el servidor de lenguaje no ata esa llamada al método. No es que Doom esté roto: es que Intelephense —como casi todos los servidores LSP— no hace ese análisis.

Investigué a fondo buscando salida:

Veredicto honesto: en este punto, PhpStorm gana de calle. Tiene un motor de análisis PHP propio (no LSP) que entiende la invocación implícita. El ecosistema LSP libre, hoy, no lo iguala. No hay botón mágico.

Pero Doom tiene algo que PhpStorm no: es moldeable

Si el servidor no es lo bastante listo, puedo enseñar al editor a hacer lo que yo haría a mano. ¿Qué hago yo cuando quiero saber quién usa un handler invokable? Busco los usos de la CLASE (de ahí salen las invocaciones). Pues que lo haga el editor solo:

(defvar my/php-magic-methods
  '("__invoke" "__construct" "__get" "__set" "__call" "__callStatic"
    "__toString" "__clone" "__destruct" "__isset" "__unset")
  "Se invocan implicitamente: pedir sus referencias por nombre da 0.")

(defun my/php--class-name-position ()
  "Posicion del NOMBRE de la clase/interface/trait/enum del buffer."
  (save-excursion
    (goto-char (point-min))
    (when (re-search-forward
           "^[[:space:]]*\\(?:\\(?:final\\|abstract\\|readonly\\)[[:space:]]+\\)*\\(?:class\\|interface\\|trait\\|enum\\)[[:space:]]+\\([A-Za-z_][A-Za-z0-9_]*\\)"
           nil t)
      (match-beginning 1))))

(defun my/lsp-smart-references ()
  "Peek de referencias; si el simbolo es un metodo magico, busca los
usos de la CLASE contenedora en su lugar."
  (interactive)
  (let ((sym (thing-at-point 'symbol t)))
    (if (and (derived-mode-p 'php-mode 'php-ts-mode)
             sym (member sym my/php-magic-methods)
             (my/php--class-name-position))
        (save-excursion
          (goto-char (my/php--class-name-position))
          (message "[smart-refs] %s es magico -> usos de la clase" sym)
          (lsp-ui-peek-find-references))
      (lsp-ui-peek-find-references))))

Ahora pones gr sobre un __invoke y, en vez de cero resultados, ves dónde se usa el handler. No es magia ni un análisis nuevo: es el editor haciendo el paso manual por ti, en transparente. No iguala el motor de PhpStorm —si el handler se inyecta por una interfaz, hay que mirar también ahí— pero cubre el caso real del 95% de las veces, y es tuyo, escrito en veinte líneas que entiendes entero.

Coda: la trampa del "numerito flotante"

Un cabo suelto, por si tropiezas con él. El número permanente que IntelliJ pinta encima del método ("7 usages" fijo, sin pedirlo) se llama CodeLens en el mundo LSP. Doom lo trae preparado, pero por más que lo activaba no salía nada. La razón: ese contador no es una pieza estándar que cualquier servidor rellene; muchos sencillamente no lo producen. Así que puedes tener el CodeLens "encendido" y ver cero números, no porque algo falle, sino porque nadie manda ese dato. Perseguí ese fantasma un rato hasta darme cuenta de que la función que de verdad uso —"enséñame los usos"— la tengo igual de bien bajo demanda. A veces la mejor optimización es saber qué no necesitas.

El patrón, una vez más

Lo que crees que te falta casi nunca falta. Está apagado, sin una tecla asignada, o se llama de una forma que no imaginabas. Y cuando de verdad falta, el editor es lo bastante tuyo como para añadírtelo.

El peek, el árbol y la lista llevaban ahí desde que instalé Doom. El parche de los métodos mágicos no estaba —pero el editor me dejó escribirlo. Esa es la diferencia que al final inclina la balanza: no tener mejor motor, sino poder abrir el capó y meter mano.

— doomguru

Comparte este post:

Es tu post

Estas seguro? Esto no se puede deshacer.

Comentarios (0)

Sin comentarios todavia. Se el primero!

Deja un comentario