emacs-lsp / lsp-ui

UI integrations for lsp-mode
https://emacs-lsp.github.io/lsp-ui
GNU General Public License v3.0
1.03k stars 141 forks source link

Sideline slow to update after executing a command using M-x ... #647

Closed simon-katz closed 2 years ago

simon-katz commented 2 years ago

My lsp-ui sideline is slow to update (takes around 2 seconds) after executing a command using M-x ..., but almost instant when I execute the same command using the keyboard.

For example, M-x next-line is slow, but down-arrow is fast.

The video below shows this.

I've tried this using https://github.com/emacs-lsp/lsp-mode/blob/master/scripts/lsp-start-plain.el and I get the same behaviour.

https://user-images.githubusercontent.com/823295/129334388-bfb22609-174f-48a2-9675-ba67cb57be10.mp4

simon-katz commented 2 years ago

I should have said, I noticed this when using flycheck-next-error and flycheck-previous-error. (More realistic than next-line.)

simon-katz commented 2 years ago

Sorry for the dribs and drabs… This is with GNU Emacs 26.3 on macOS 10.15.7 (Catalina).

simon-katz commented 2 years ago

I've investigated. Looks like it's a general Emacs issue with (add-hook 'post-command-hook ...).

See https://github.com/simon-katz/demo-post-command-hook-slow-with-m-x-commands.el/blob/main/demo-post-command-hook-slow-with-m-x-commands.el

I guess I need to report this to the Emacs maintainers. When I've done that I'll add a comment here. Closing this now.

simon-katz commented 2 years ago

Reported as an Emacs bug at https://debbugs.gnu.org/cgi/bugreport.cgi?bug=50042

ericdallo commented 2 years ago

@simon-katz I'd try recent emacs versions as well, like emacs 27 or even 28 to check if it was fixed

simon-katz commented 2 years ago

@ericdallo I did try Emacs 27 — not fixed.

The Emacs bug report (link above) has a response suggesting it hasn't been fixed since then, either.

simon-katz commented 2 years ago

I've hacked a fix that wraps a sit-for inside a run-at-time. Seems to work OK.

See https://github.com/simon-katz/nomis-emacs-configuration/blob/2021-08-11-update-all-packages/emacs-installation/emacs-init-files/nomis-fix-post-command-hook-slow-with-m-x-commands.el

(Permalink in case I change things: https://github.com/simon-katz/nomis-emacs-configuration/blob/be54e829bd91b6f218884dcc54bce62c1f795d5a/emacs-installation/emacs-init-files/nomis-fix-post-command-hook-slow-with-m-x-commands.el#L1)

simon-katz commented 2 years ago

This is now fixed in Emacs — at commit https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=00a9c50ad7c82f72b422100624f7f125d717c00f

The commit makes some changes to execute-extended-command.

If, like me, you don't want to wait for Emacs 28, you can use this (copied from https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/simple.el?id=00a9c50ad7c82f72b422100624f7f125d717c00f#n2197):

(defvar execute-extended-command--binding-timer nil)

(defun execute-extended-command (prefixarg &optional command-name typed)
  ;; Based on Fexecute_extended_command in keyboard.c of Emacs.
  ;; Aaron S. Hawley <aaron.s.hawley(at)gmail.com> 2009-08-24
  "Read a command name, then read the arguments and call the command.
To pass a prefix argument to the command you are
invoking, give a prefix argument to `execute-extended-command'."
  (declare (interactive-only command-execute))
  ;; FIXME: Remember the actual text typed by the user before completion,
  ;; so that we don't later on suggest the same shortening.
  (interactive
   (let ((execute-extended-command--last-typed nil))
     (list current-prefix-arg
           (read-extended-command)
           execute-extended-command--last-typed)))
  ;; Emacs<24 calling-convention was with a single `prefixarg' argument.
  (unless command-name
    (let ((current-prefix-arg prefixarg) ; for prompt
          (execute-extended-command--last-typed nil))
      (setq command-name (read-extended-command))
      (setq typed execute-extended-command--last-typed)))
  (let* ((function (and (stringp command-name) (intern-soft command-name)))
         (binding (and suggest-key-bindings
               (not executing-kbd-macro)
               (where-is-internal function overriding-local-map t)))
         (delay-before-suggest 0)
         (find-shorter nil))
    (unless (commandp function)
      (error "`%s' is not a valid command name" command-name))
    ;; Some features, such as novice.el, rely on this-command-keys
    ;; including M-x COMMAND-NAME RET.
    (set--this-command-keys (concat "\M-x" (symbol-name function) "\r"))
    (setq this-command function)
    ;; Normally `real-this-command' should never be changed, but here we really
    ;; want to pretend that M-x <cmd> RET is nothing more than a "key
    ;; binding" for <cmd>, so the command the user really wanted to run is
    ;; `function' and not `execute-extended-command'.  The difference is
    ;; visible in cases such as M-x <cmd> RET and then C-x z (bug#11506).
    (setq real-this-command function)
    (let ((prefix-arg prefixarg))
      (command-execute function 'record))
    ;; Ensure that we never have two of the suggest-binding timers in
    ;; flight.
    (when execute-extended-command--binding-timer
      (cancel-timer execute-extended-command--binding-timer))
    ;; If this command displayed something in the echo area; then
    ;; postpone display our suggestion message a bit.
    (when (and suggest-key-bindings
               (or binding
                   (and extended-command-suggest-shorter typed)))
      (setq delay-before-suggest
            (cond
             ((zerop (length (current-message))) 0)
             ((numberp suggest-key-bindings) suggest-key-bindings)
             (t 2)))
      (when (and extended-command-suggest-shorter
                 (not binding)
                 (not executing-kbd-macro)
                 (symbolp function)
                 (> (length (symbol-name function)) 2))
        ;; There's no binding for CMD.  Let's try and find the shortest
        ;; string to use in M-x.
        (setq find-shorter t))
      (when (or binding find-shorter)
        (setq execute-extended-command--binding-timer
              (run-at-time
               delay-before-suggest nil
               (lambda ()
                 ;; If the user has typed any other commands in the
                 ;; meantime, then don't display anything.
                 (when (eq function real-last-command)
                   ;; Find shorter string.
                   (when find-shorter
                     (while-no-input
                       ;; FIXME: Can be slow.  Cache it maybe?
                       (setq binding (execute-extended-command--shorter
                                      (symbol-name function) typed))))
                   (when binding
                     (with-temp-message
                         (format-message "You can run the command `%s' with %s"
                                         function
                                         (if (stringp binding)
                                             (concat "M-x " binding " RET")
                                           (key-description binding)))
                       (sit-for (if (numberp suggest-key-bindings)
                                    suggest-key-bindings
                                  2))))))))))))