branch: externals/phpinspect commit c24115444d258df139a9343a1e647e3d696edc01 Author: Hugo Thunnissen <de...@hugot.nl> Commit: Hugo Thunnissen <de...@hugot.nl>
Fix bug in buffer-shadow synchronization + make eldoc function async --- phpinspect-autoload.el | 19 ++++--- phpinspect-buffer.el | 130 +++++++++++++++++++++++++++----------------- phpinspect-eldoc.el | 26 ++++++--- phpinspect-project.el | 4 ++ phpinspect-property-cell.el | 10 ++-- phpinspect-thread.el | 9 ++- phpinspect-typedef.el | 23 ++++++-- phpinspect.el | 3 +- test/test-buffer.el | 56 +++++++++++++++++++ test/test-eldoc.el | 4 +- 10 files changed, 202 insertions(+), 82 deletions(-) diff --git a/phpinspect-autoload.el b/phpinspect-autoload.el index f71a780b4b..962ed2ca78 100644 --- a/phpinspect-autoload.el +++ b/phpinspect-autoload.el @@ -473,19 +473,20 @@ FILE-NAME does not contain any wildcards, instead of nil." (phpinspect--log "Number of autoload strategies in batch: %s" (length batch)) (phpinspect-pipeline-emit-all batch)))) - -(cl-defmethod phpinspect-autoloader-resolve ((autoloader phpinspect-autoloader) - (type-name (head phpinspect-name))) - ;; Wait for pending refresh if not running in main thread. - (unless (eq main-thread (current-thread)) - (when (and (phpinspect-autoloader-refresh-thread autoloader) - (thread-live-p (phpinspect-autoloader-refresh-thread autoloader))) +(defun phpinspect-autoloader-await-refresh (autoloader) + (when-let ((refresh-thread (phpinspect-autoloader-refresh-thread autoloader)) + ((thread-live-p refresh-thread))) + (unless (eq main-thread (current-thread)) (phpinspect--log "Pausing thread %s to await autoload refresh completion" (thread-name (current-thread))) - (thread-join (phpinspect-autoloader-refresh-thread autoloader)) + (thread-join refresh-thread) (phpinspect--log "Autoload refresh completed, continuing waiting thread %s" - (thread-name (current-thread))))) + (thread-name (current-thread)))))) + +(cl-defmethod phpinspect-autoloader-resolve ((autoloader phpinspect-autoloader) + (type-name (head phpinspect-name))) + (phpinspect-autoloader-await-refresh autoloader) (or (gethash type-name (phpinspect-autoloader-own-types autoloader)) (gethash type-name (phpinspect-autoloader-types autoloader)))) diff --git a/phpinspect-buffer.el b/phpinspect-buffer.el index ec42ce6b75..860c1aff6d 100644 --- a/phpinspect-buffer.el +++ b/phpinspect-buffer.el @@ -78,6 +78,10 @@ emacs buffer." (phpinspect--cache-get-project-create (phpinspect--get-or-create-global-cache) (phpinspect-current-project-root))))) +(defun phpi-shadow-kill (shadow) + (phpi-thread-kill (phpi-shadow-thread shadow)) + (kill-buffer (phpi-shadow-buffer shadow))) + (defun phpinspect-buffer-tainted-p (buffer) "Whether or not BUFFER's current tree needs updating to incorporate edits." (not (phpi-shadow-synced-p (phpinspect-buffer-shadow buffer)))) @@ -187,7 +191,7 @@ tokens that have been deleted from a buffer." (phpinspect-buffer-project buffer) (car var)))) (if-let ((token-meta (phpinspect-buffer-token-meta buffer token))) - (phpi-typedef-delete-property-token-definition class (phpi-var-name (car var)) token-meta) + (phpi-typedef-delete-property-token-definition class (phpi-var-name (cdr var)) token-meta) (phpi-typedef-delete-property class (cdr var)))))) ((or (phpinspect-this-p token) (phpinspect-attrib-p token)) (phpinspect-buffer--delete-dynamic-prop-index-reference buffer token)) @@ -237,10 +241,14 @@ tokens that have been deleted from a buffer." (phpi-typedef-delete-property-token-definition typedef (cadr ref) (phpinspect-meta-token (car (last ref)))))) -(cl-defmethod phpinspect-buffer-reset ((buffer phpinspect-buffer)) +(defun phpinspect-buffer-reset (buffer) "Clear all metadata stored in BUFFER." + (interactive (list phpinspect-current-buffer)) (phpinspect-buffer--clear-query-cache buffer) + (phpi-shadow-kill (phpinspect-buffer-shadow buffer)) + (phpinspect-make-shadow buffer) + (setf (phpinspect-buffer-tree buffer) nil (phpinspect-buffer--tokens buffer) nil (phpinspect-buffer-map buffer) nil @@ -523,7 +531,7 @@ DECLARATION must be an object of type `phpinspect-meta'." (defun phpinspect-buffer--index-class-variable (buffer var) (let ((class (phpinspect-buffer-find-token-ancestor-matching buffer var #'phpinspect-class-p)) - scope static indexed comment-before) + scope static comment-before) (if (phpinspect-static-p (phpinspect-meta-token (phpinspect-meta-parent var))) ;; Variable is defined as [scope?] static [type?] $variable (progn @@ -558,24 +566,24 @@ DECLARATION must be an object of type `phpinspect-meta'." (typedef (phpinspect-buffer-get-typedef-for-class-token buffer class))) - (setq indexed - (phpinspect-make-property - (phpi-typedef-name typedef) - (if (phpinspect-const-p (phpinspect-meta-token var)) - (phpinspect--index-const-from-scope scope) + (when-let ((indexed + (phpinspect-make-property + (phpi-typedef-name typedef) + (if (phpinspect-const-p (phpinspect-meta-token var)) + (phpinspect--index-const-from-scope scope) - (phpinspect--index-variable-from-scope - type-resolver - scope - (and (phpinspect-comment-p comment-before) comment-before) - static)))) + (phpinspect--index-variable-from-scope + type-resolver + scope + (and (phpinspect-comment-p comment-before) comment-before) + static))))) - (phpi-prop-add-definition-token indexed var) - (phpi-typedef-set-property typedef indexed) + (phpi-prop-add-definition-token indexed var) + (phpi-typedef-set-property typedef indexed) - (phpinspect-buffer-set-index-reference-for-token - buffer (phpinspect-meta-token var) - (cons (phpi-typedef-name typedef) indexed))))) + (phpinspect-buffer-set-index-reference-for-token + buffer (phpinspect-meta-token var) + (cons (phpi-typedef-name typedef) indexed)))))) (cl-defmethod phpinspect-buffer-update-project-index ((buffer phpinspect-buffer)) "Update project index using the last parsed token map of this @@ -583,8 +591,11 @@ buffer. When `phpinspect-buffer-parse' has been executed before and a map is available from the previous parse, this is used. If none is available `phpinspect-buffer-parse' is called before continuing execution." - - (phpi-shadow-await-index-synced (phpinspect-buffer-shadow buffer) t)) + ;; Wait until project autoloader is ready, this is more efficient than waiting + ;; for a shadow thread, which would be blocked while waiting for the + ;; autoloader regardless. + (phpinspect-project-await-autoload (phpinspect-buffer-project buffer)) + (phpi-shadow-await-index-synced (phpinspect-buffer-shadow buffer))) (defun phpinspect-buffer-parse-map (buffer) (phpinspect-buffer-parse buffer) @@ -640,7 +651,7 @@ use." (phpinspect-meta-end meta))))) (defun phpinspect-buffer-root-meta (buffer) - (phpi-shadow-await-synced (phpinspect-buffer-shadow buffer) t) + (phpi-shadow-await-synced (phpinspect-buffer-shadow buffer)) (phpinspect-bmap-root-meta (phpinspect-buffer-map buffer))) (defun phpinspect-display-buffer-tree () @@ -677,10 +688,7 @@ use." (defun phpinspect-buffer-kill () (when phpinspect-current-buffer - (kill-buffer - (phpinspect-buffer-shadow - (phpinspect-buffer-shadow - phpinspect-current-buffer))))) + (phpi-shadow-kill (phpinspect-buffer-shadow phpinspect-current-buffer)))) (defun phpinspect-claim-buffer (buffer &optional project) "Setup an instance of `phpinspect-buffer' for BUFFER. @@ -693,15 +701,19 @@ and adds `phpinspect-after-change-function' to buffer-local BUFFER must be a normal emacs buffer. If provided, PROJECT must be an instance of `phpinspect-project'." (with-current-buffer buffer - (setq-local phpinspect-current-buffer - (phpinspect-make-buffer :buffer buffer :-project project)) - (setf (phpinspect-buffer-shadow phpinspect-current-buffer) - (phpinspect-make-shadow phpinspect-current-buffer)) + (let ((phpi-buffer + (phpinspect-make-buffer :buffer buffer :-project project))) + + (phpinspect-make-shadow phpi-buffer) + + ;(message "Shadow: %s" (not (not (phpinspect-buffer-shadow phpi-buffer)))) - (add-hook 'after-change-functions #'phpinspect-after-change-function nil t) - (add-hook 'kill-buffer-hook #'phpinspect-buffer-kill) + (setq-local phpinspect-current-buffer phpi-buffer) - phpinspect-current-buffer)) + (add-hook 'after-change-functions #'phpinspect-after-change-function nil t) + (add-hook 'kill-buffer-hook #'phpinspect-buffer-kill) + + phpinspect-current-buffer))) ;;;;;;;;;; SHADOWING ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -867,25 +879,39 @@ If provided, PROJECT must be an instance of `phpinspect-project'." (phpi-shadow-await-predicate shadow #'phpi-shadow-synced-p allow-interrupt)) (defun phpi-shadow-await-index-synced (shadow &optional allow-interrupt) - (phpi-shadow-await-predicate shadow #'phpi-shadow-index-synced-p allow-interrupt)) + (phpi-shadow-await-predicate shadow #'phpi-shadow-index-synced-p allow-interrupt)) + +(defun phpi--handle-use-trait-deletion (buffer deletion) + (when-let ((class (seq-find (phpinspect-meta-token-predicate #'phpinspect-class-p) + (phpinspect-buffer-tokens-enclosing-point + buffer (phpinspect-meta-start deletion)))) + (declaration (phpinspect-meta-find-first-child-matching-token + class #'phpinspect-class-declaration-p))) + (phpinspect-buffer--index-class-declaration buffer declaration 'force))) (defun phpi-shadow-process-deletions (shadow) - (let ((buffer (phpi-shadow-origin shadow))) + (let ((buffer (phpi-shadow-origin shadow)) + (additions (phpi-shadow--additions shadow))) ;; Process deleted tokens (dolist (deletion (phpi-shadow--deletions shadow)) (phpi-progn - (pcase (phpinspect-meta-token deletion) - ((pred phpinspect--can-delete-buffer-index-for-token) - (phpinspect-buffer-delete-index-for-token buffer (phpinspect-meta-token deletion))) - ((pred phpinspect-use-trait-p) - (when-let ((class (seq-find (phpinspect-meta-token-predicate #'phpinspect-class-p) - (phpinspect-buffer-tokens-enclosing-point - buffer (phpinspect-meta-start deletion)))) - (declaration (phpinspect-meta-find-first-child-matching-token - class #'phpinspect-class-declaration-p))) - (phpinspect-buffer--index-class-declaration buffer declaration 'force)))) - - (remhash (phpinspect-meta-token deletion) (phpinspect-buffer--tokens buffer)))) + (let ((token (phpinspect-meta-token deletion))) + (if (gethash token additions) + ;; Token was deleted before it could be indexed, remove and + ;; continue + (remhash token additions) + + (progn + ;; Token was indexed but has been deleted, update index accordingly + (pcase (phpinspect-meta-token deletion) + ((pred phpinspect--can-delete-buffer-index-for-token) + (phpinspect-buffer-delete-index-for-token + buffer (phpinspect-meta-token deletion))) + + ((pred phpinspect-use-trait-p) + (phpi--handle-use-trait-deletion buffer deletion))) + + (remhash (phpinspect-meta-token deletion) (phpinspect-buffer--tokens buffer))))))) (setf (phpi-shadow--deletions shadow) nil))) @@ -934,9 +960,13 @@ If provided, PROJECT must be an instance of `phpinspect-project'." (phpi-shadow--indexed-change shadow)))) (defun phpi-shadow-make-job-queue (shadow) - (phpi-start-job-queue (format " **phpinspect-shadow-thread**<%d>" (phpi-shadow-id shadow)) - (lambda (job) - (phpi-shadow--handle-job shadow job)))) + ;; Make sure that the thread uses shadow buffer as its current buffer. This + ;; prevents the user-edited buffer from becoming unkillable (buffers with + ;; active threads cannot be killed). + (with-current-buffer (phpi-shadow-buffer shadow) + (phpi-start-job-queue (format " **phpinspect-shadow-thread**<%d>" (phpi-shadow-id shadow)) + (lambda (job) + (phpi-shadow--handle-job shadow job))))) (defun phpinspect-make-shadow (origin) (cl-assert (phpinspect-buffer-p origin)) @@ -948,6 +978,8 @@ If provided, PROJECT must be an instance of `phpinspect-project'." (format " **phpinspect-shadow**<%d>" id)) :id id))) + (setf (phpinspect-buffer-shadow origin) shadow) + ;; Copy buffer contents (with-current-buffer (phpi-shadow-buffer shadow) (insert (phpinspect-with-current-buffer origin (buffer-string)))) diff --git a/phpinspect-eldoc.el b/phpinspect-eldoc.el index 7be4f4809b..35307e66c0 100644 --- a/phpinspect-eldoc.el +++ b/phpinspect-eldoc.el @@ -27,6 +27,7 @@ (require 'phpinspect-token-predicates) (require 'phpinspect-resolve) (require 'phpinspect-buffer) +(require 'phpinspect-thread) (eval-when-compile (phpinspect--declare-log-group 'eldoc)) @@ -283,7 +284,17 @@ also `phpinspect-eldoc-query-execute'.") (remove nil responses))) -(defun phpinspect-eldoc-function () +(defun phpinspect--eldoc-function-sync () + (catch 'phpinspect-interrupted + (when-let ((resp (phpinspect-eldoc-query-execute + (phpinspect-make-eldoc-query + :buffer phpinspect-current-buffer + :point (phpinspect--determine-completion-point))))) + (mapconcat #'phpinspect-eldoc-string resp "\n")))) + +(defvar-local phpinspect--eldoc-thread nil) + +(defun phpinspect-eldoc-function (callback) "An `eldoc-documentation-function` implementation for PHP files. Ignores `eldoc-argument-case` and `eldoc-echo-area-use-multiline-p`. @@ -291,13 +302,12 @@ Ignores `eldoc-argument-case` and `eldoc-echo-area-use-multiline-p`. TODO: - Respect `eldoc-echo-area-use-multiline-p` " - (catch 'phpinspect-interrupted - (let ((resp (phpinspect-eldoc-query-execute - (phpinspect-make-eldoc-query - :buffer phpinspect-current-buffer - :point (phpinspect--determine-completion-point))))) - (when resp - (mapconcat #'phpinspect-eldoc-string resp "\n"))))) + (when phpinspect-current-buffer + (phpi-run-threaded "PHPInspect Eldoc" + (while-no-input + (let ((result (phpinspect--eldoc-function-sync))) + (funcall callback result)))) + 'async)) (provide 'phpinspect-eldoc) diff --git a/phpinspect-project.el b/phpinspect-project.el index ce8552e30c..582b7bbbff 100644 --- a/phpinspect-project.el +++ b/phpinspect-project.el @@ -319,6 +319,10 @@ before the search is executed." (phpinspect-project-get-type-filepath project type 'index-new))) result)))) +(defun phpinspect-project-await-autoload (project) + (when-let ((autoload (phpinspect-project-autoload project))) + (phpinspect-autoloader-await-refresh autoload))) + (cl-defmethod phpinspect-project-index-type-file ((project phpinspect-project) (type phpinspect--type)) "Index the file that TYPE is expected to be defined in." diff --git a/phpinspect-property-cell.el b/phpinspect-property-cell.el index ebdefd3173..767bbeb30d 100644 --- a/phpinspect-property-cell.el +++ b/phpinspect-property-cell.el @@ -45,10 +45,12 @@ :type phpinspect--variable)) (defun phpinspect-make-property (origin-type definition) - (phpinspect-make-property-generated - :name (phpinspect-intern-name (phpinspect--variable-name definition)) - :origin-type origin-type - :definition definition)) + ;; A property cannot have a nil name. + (when (phpinspect--variable-name definition) + (phpinspect-make-property-generated + :name (phpinspect-intern-name (phpinspect--variable-name definition)) + :origin-type origin-type + :definition definition))) (defun phpi-prop-delete-definition-token (prop token) (setf (phpi-prop-definition-tokens prop) diff --git a/phpinspect-thread.el b/phpinspect-thread.el index b55e45d470..d16ecd4f20 100644 --- a/phpinspect-thread.el +++ b/phpinspect-thread.el @@ -71,7 +71,8 @@ (defvar phpinspect--main-thread-starving (phpi-make-condition 'no)) (defun phpi-thread-kill (thread) - (thread-signal thread 'phpinspect-kill-thread nil)) + (when (thread-live-p thread) + (thread-signal thread 'phpinspect-kill-thread nil))) (defmacro phpi-run-threaded (thread-name &rest body) (declare (indent 1)) @@ -79,7 +80,8 @@ `(make-thread (lambda () (condition-case ,err-sym - (progn ,@body) + (let ((inhibit-quit t)) + (progn ,@body)) (phpinspect-kill-thread) (error (phpinspect-message @@ -153,7 +155,8 @@ If current thread is the main thread, this function does nothing." (setf (phpi-job-queue-thread queue) (phpi-run-threaded (format "(job queue) %s" name) - (let ((ended nil)) + (let ((ended nil) + (inhibit-quit t)) (catch 'phpi--break (while t (if-let ((job (phpinspect-queue-dequeue queue))) diff --git a/phpinspect-typedef.el b/phpinspect-typedef.el index 0302cb00cb..5ef1b3c5fb 100644 --- a/phpinspect-typedef.el +++ b/phpinspect-typedef.el @@ -437,8 +437,9 @@ them, which are then incorporated into DEF's properties." (setf (phpi-typedef-initial-index def) t) (let-alist index - (dolist (var (append .variables .constants .static-variables)) - (phpi-pcol-add pcol (phpinspect-make-property home-type var)))) + (dolist (var (append .variables .constants .static-variables)) + (when-let ((prop (phpinspect-make-property home-type var))) + (phpi-pcol-add pcol prop)))) (phpi-typedef-update-extensions def `(,@(alist-get 'implements index) @@ -462,8 +463,9 @@ them, which are then incorporated into DEF's properties." (cl-defmethod phpi-typedef-set-property ((def phpinspect-typedef) (var phpinspect--variable)) - (phpi-typedef-set-property - def (phpinspect-make-property (phpi-typedef-name def) var))) + (when-let ((prop (phpinspect-make-property (phpi-typedef-name def) var))) + + (phpi-typedef-set-property def prop))) (cl-defmethod phpi-typedef-delete-property ((def phpinspect-typedef) (name (head phpinspect-name)) @@ -487,12 +489,18 @@ them, which are then incorporated into DEF's properties." (cl-defmethod phpi-typedef-delete-property-token-definition ((def phpinspect-typedef) (prop-name string) token-meta) + (phpi-typedef-delete-property-token-definition + def (phpinspect-intern-name prop-name) token-meta)) + +(cl-defmethod phpi-typedef-delete-property-token-definition + ((def phpinspect-typedef) (prop-name (head phpinspect-name)) token-meta) (when-let ((prop (phpi-typedef-get-property def prop-name))) (phpi-prop-delete-definition-token prop token-meta) (unless (phpi-prop-definition-tokens prop) (phpi-typedef-delete-property def prop)))) + (cl-defmethod phpi-typedef-get-properties ((def phpinspect-typedef)) (seq-filter #'phpi-prop-vanilla-p (phpi-pcol-list-active (phpi-typedef-properties def)))) @@ -507,8 +515,13 @@ them, which are then incorporated into DEF's properties." (cl-defmethod phpi-typedef-get-property ((def phpinspect-typedef) (prop-name string)) + (phpi-typedef-get-property def (phpinspect-intern-name prop-name))) + +(cl-defmethod phpi-typedef-get-property + ((def phpinspect-typedef) (prop-name (head phpinspect-name))) (phpi-pcol-get-active-property - (phpi-typedef-properties def) (phpinspect-intern-name prop-name))) + (phpi-typedef-properties def) prop-name)) + (defun phpi-typedef-get-dependencies (def) "Gets types that DEF directly depends on. diff --git a/phpinspect.el b/phpinspect.el index 8068b413ee..50f79b4bbc 100644 --- a/phpinspect.el +++ b/phpinspect.el @@ -144,8 +144,7 @@ (add-hook 'completion-at-point-functions #'phpinspect-complete-at-point nil 'local) - (set (make-local-variable 'eldoc-documentation-function) - #'phpinspect-eldoc-function) + (add-to-list 'eldoc-documentation-functions #'phpinspect-eldoc-function) (make-local-variable 'eldoc-message-commands) (eldoc-add-command 'c-electric-paren) diff --git a/test/test-buffer.el b/test/test-buffer.el index d426ac6297..003a886d6a 100644 --- a/test/test-buffer.el +++ b/test/test-buffer.el @@ -513,6 +513,62 @@ public $banana; const CONSTANT = 0; (phpinspect--make-type :name "\\TestClass")) "banana"))))))) +(ert-deftest phpinspect-buffer-index-class-variable-incrementally () + (with-temp-buffer + (let ((buffer (phpinspect-claim-buffer (current-buffer) (phpinspect--make-dummy-project)))) + (insert "<?php class TestClass { +public array $banana; +}") + + (phpinspect-buffer-update-project-index buffer) + + (should (phpinspect--type= (phpinspect--make-type :name "\\array") + (phpi-var-type + (phpi-typedef-get-property + (phpinspect-project-get-typedef + (phpinspect-buffer-project buffer) + (phpinspect--make-type :name "\\TestClass")) + "banana")))) + + (goto-char 45) + (insert "ra") + (insert "ma") + (phpinspect-buffer-parse buffer) + (insert "ma") + (phpinspect-buffer-parse buffer) + + (phpinspect-buffer-update-project-index buffer) + (should-not (phpi-typedef-get-property + (phpinspect-project-get-typedef + (phpinspect-buffer-project buffer) + (phpinspect--make-type :name "\\TestClass")) + "banana")) + + (should-not (phpi-typedef-get-property + (phpinspect-project-get-typedef + (phpinspect-buffer-project buffer) + (phpinspect--make-type :name "\\TestClass")) + "bananara")) + + (should-not (phpi-typedef-get-property + (phpinspect-project-get-typedef + (phpinspect-buffer-project buffer) + (phpinspect--make-type :name "\\TestClass")) + "bananarama")) + + (should (= 1 (length (phpi-typedef-get-properties + (phpinspect-project-get-typedef + (phpinspect-buffer-project buffer) + (phpinspect--make-type :name "\\TestClass")))))) + + (should (phpinspect--type= (phpinspect--make-type :name "\\array") + (phpi-var-type + (phpi-typedef-get-property + (phpinspect-project-get-typedef + (phpinspect-buffer-project buffer) + (phpinspect--make-type :name "\\TestClass")) + "bananaramama"))))))) + (ert-deftest phpinspect-buffer-index-typehinted-class-variables () (with-temp-buffer (let ((buffer (phpinspect-claim-buffer diff --git a/test/test-eldoc.el b/test/test-eldoc.el index 5752266e35..0768782aa6 100644 --- a/test/test-eldoc.el +++ b/test/test-eldoc.el @@ -105,7 +105,7 @@ class Thing (setq-local phpinspect-current-buffer (phpinspect-claim-buffer (current-buffer) project)) (phpinspect-buffer-parse phpinspect-current-buffer) - (phpinspect-eldoc-function)))))) + (phpinspect--eldoc-function-sync)))))) (ert-deftest phpinspect-eldoc-function-for-static-method () (phpinspect-purge-cache) @@ -131,4 +131,4 @@ class Thing (insert php-code) (setq-local phpinspect-current-buffer (phpinspect-claim-buffer (current-buffer))) - (phpinspect-eldoc-function)))))) + (phpinspect--eldoc-function-sync))))))