branch: externals/phpinspect commit b440807be55f2dd10b87a8498934352b15dd1c12 Author: Hugo Thunnissen <de...@hugot.nl> Commit: Hugo Thunnissen <de...@hugot.nl>
Implement computed-on-demand dynamic completion table This is a massive quality of life improvement that seems to remove almost all editing latency that was introduced by triggering of completion functions while editing the buffer. --- phpinspect-buffer.el | 14 +++- phpinspect-completion.el | 211 +++++++++++++++++++++-------------------------- phpinspect-thread.el | 19 +++-- test/test-completion.el | 19 ++--- 4 files changed, 125 insertions(+), 138 deletions(-) diff --git a/phpinspect-buffer.el b/phpinspect-buffer.el index caaab766d0..b5689fe32a 100644 --- a/phpinspect-buffer.el +++ b/phpinspect-buffer.el @@ -747,6 +747,7 @@ If provided, PROJECT must be an instance of `phpinspect-project'." (queue nil :type phpinspect--queue) (thread nil :type thread) (id nil :type integer) + (-index-waiting-thread nil) (-last-change (phpi-make-condition)) (-synced-change (phpi-make-condition)) (-indexed-change (phpi-make-condition)) @@ -866,7 +867,17 @@ indexation")) ('parse-fresh (phpi-shadow-parse-fresh shadow)) ('update-project-index - (phpi-shadow-update-project-index shadow)) + (let ((autoloader (phpinspect-project-autoload + (phpinspect-buffer-project + (phpi-shadow-origin shadow))))) + (if (phpinspect-autoloader-refreshing-p autoloader) + (unless (and-let* ((thread (phpi-shadow--index-waiting-thread shadow)) + ((thread-live-p thread)))) + (setf (phpi-shadow--index-waiting-thread shadow) + (phpi-run-threaded "shadow-index-notifier" + (phpinspect-autoloader-await-refresh autoloader) + (phpi-shadow-enqueue-task shadow 'update-project-index)))) + (phpi-shadow-update-project-index shadow)))) (_ (phpinspect-message "Shadow thread received unsupported task type: %s" @@ -902,6 +913,7 @@ indexation")) (defun phpi-shadow-await-synced (shadow &optional _allow-interrupt) (phpi-shadow-assert-live-p shadow) + (unless (phpi-shadow-is-me-p shadow) (phpi-condition-wait (phpi-shadow--synced-change shadow) (lambda (change) diff --git a/phpinspect-completion.el b/phpinspect-completion.el index ea70f752f0..a476057b3a 100644 --- a/phpinspect-completion.el +++ b/phpinspect-completion.el @@ -51,19 +51,45 @@ "Creates a `phpinspect--completion` for a possible completion candidate. Candidates can be indexed functions and variables.") -(cl-defstruct (phpinspect--completion-list - (:constructor phpinspect--make-completion-list)) + +(cl-defstruct (phpi-comp-result + (:constructor phpi-make-comp-result)) "Contains all data for a completion at point" (completion-start nil :type integer) (completion-end nil :type integer) + (query nil + :type phpinspect-completion-query) + (rctx nil + :type phpinspect--resolvecontext) + (strategy nil + :type list) + + (-list nil + :type phpinspect--completion-list)) + +(cl-defstruct (phpinspect--completion-list + (:constructor phpinspect--make-completion-list)) + "Contains all data for a completion at point" (completions (make-hash-table :test #'equal :size 10000 :rehash-size 2) :type hash-table :documentation "A list of completion strings") (has-candidates nil)) +(defun phpi-comp-result-list (result) + (with-memoization (phpi-comp-result--list result) + (let ((clist (phpinspect--make-completion-list))) + (when (phpi-comp-result-strategy result) + (dolist (candidate (phpinspect-comp-strategy-execute + (phpi-comp-result-strategy result) + (phpi-comp-result-query result) + (phpi-comp-result-rctx result))) + (phpinspect--completion-list-add clist (phpinspect--make-completion candidate)))) + + clist))) + (cl-defgeneric phpinspect--completion-list-add (comp-list completion) "Add a completion to a completion-list.") @@ -307,86 +333,40 @@ completion result.") "Execute QUERY. Returns list of `phpinspect--completion'." - (let* ((last-parameters phpinspect--last-completion-parameters) - ;; Check if caching is at all possible, before parsing the buffer. This - ;; needs to happen now, as tainted regions are removed from the taint - ;; pool after a buffer parse. We need the tainted region to determine - ;; if the only edit is one performed during completion. - (maybe-cache? (and - last-parameters - (phpinspect--completion-query-maybe-should-cache - (phpinspect--completion-parameters-query last-parameters) - query)))) - - (let* ((buffer (phpinspect-completion-query-buffer query)) - (point (phpinspect-completion-query-point query)) - (buffer-map (phpinspect-buffer-parse-map buffer)) - (rctx (phpinspect-get-resolvecontext - (phpinspect-buffer-project buffer) buffer-map point)) - (completion-list (phpinspect--make-completion-list))) - - (phpinspect-buffer-update-project-index buffer) - - (setq phpinspect--last-completion-list - (catch 'phpinspect--return - (dolist (strategy phpinspect-completion-strategies) - (when-let (region (phpinspect-comp-strategy-supports strategy query rctx)) - ;; Check if using the cached completion list is possible. - (if-let ((nil) - (maybe-cache?) - ;; There is a previous list available - (last-list phpinspect--last-completion-list) - ;; The list had candidates in it - ((phpinspect--completion-list-has-candidates last-list)) - ;; The subject of the last resolvecontext is the same - ;; (so likely to evaluate to the same results). - ((equal (butlast (phpinspect--resolvecontext-subject rctx)) - (butlast (phpinspect--completion-parameters-subject - last-parameters)))) - ;; The completion strategy is the same as the last - ;; one used. - ((eq strategy - (phpinspect--completion-parameters-strategy - last-parameters)))) - ;; We can safely use the cached list: All parameters used - ;; for the last completion seem to match the current one. - (progn - ;; Update the region, this is necessary for - ;; completion-at-point to determine what is being - ;; completed. - (setf (phpinspect--completion-list-completion-start last-list) - (car region) - (phpinspect--completion-list-completion-end last-list) - (cadr region)) - - (throw 'phpinspect--return last-list)) - - ;; We can't use the cached list, proceed executing the - ;; strategy. - (setf (phpinspect--completion-list-completion-start completion-list) - (car region) - (phpinspect--completion-list-completion-end completion-list) - (cadr region)) - - ;; update last used parameters - (setq phpinspect--last-completion-parameters - (phpinspect--make-completion-parameters - :subject (phpinspect--resolvecontext-subject rctx) - :strategy strategy - :query query)) - - (phpinspect--log "Found matching completion strategy. Executing...") - (dolist (candidate (phpinspect-comp-strategy-execute strategy query rctx)) - (phpinspect--completion-list-add - completion-list (phpinspect--make-completion candidate))) - - (throw 'phpinspect--return completion-list)))) - ;; return empty list - completion-list)))) - - (phpinspect--log "Returning completion list %s" phpinspect--last-completion-list) - phpinspect--last-completion-list) - + (let* (result thread) + + (setq thread + (phpi-run-threaded "completion" + (setq result + (let* ((buffer (phpinspect-completion-query-buffer query)) + (point (phpinspect-completion-query-point query)) + (buffer-map (phpinspect-buffer-parse-map buffer)) + (rctx (phpinspect-get-resolvecontext + (phpinspect-buffer-project buffer) buffer-map point))) + + (catch 'phpinspect--return + (dolist (strategy phpinspect-completion-strategies) + (when-let (region (phpinspect-comp-strategy-supports strategy query rctx)) + (throw 'phpinspect--return + (phpi-make-comp-result + :completion-start (phpinspect-region-start region) + :completion-end (phpinspect-region-end region) + :rctx rctx + :query query + :strategy strategy))))))))) + + (let ((cancelled t)) + (with-timeout (0.2) + (while-no-input + (while (and (thread-live-p thread) (not (input-pending-p t))) + (sleep-for 0.02)) + (setq cancelled nil))) + + (if cancelled + (phpi-make-comp-result + :completion-start (point) + :completion-end (point)) + result)))) ;; FIXME: completions should be stored in an LRU cache keyed with object ;; pointers of the things they represent (variables/functions). Re-creating them @@ -466,20 +446,20 @@ Returns list of `phpinspect--completion'." ('variable "<va> "))))) -(defun phpinspect--get-completion-at-point () +(defun phpinspect-complete-at-point () (catch 'phpinspect-interrupted - (let ((comp-list (phpinspect-completion-query-execute (phpinspect--get-completion-query)))) - - (and (phpinspect--completion-list-has-candidates comp-list) - (list (phpinspect--completion-list-completion-start comp-list) - (phpinspect--completion-list-completion-end comp-list) + (when-let ((result (phpinspect-completion-query-execute (phpinspect--get-completion-query)))) + (list (phpi-comp-result-completion-start result) + (phpi-comp-result-completion-end result) (completion-table-dynamic (lambda (_) - (phpinspect--completion-list-strings comp-list))) + (phpinspect--completion-list-strings + (phpi-comp-result-list result)))) :affixation-function (lambda (completions) (let (affixated completion) (dolist (comp completions) - (setq completion (phpinspect--completion-list-get-metadata comp-list comp)) + (setq completion (phpinspect--completion-list-get-metadata + (phpi-comp-result-list result) comp)) (push (list comp (phpinspect--prefix-for-completion completion) (concat " " (phpinspect--completion-meta completion))) affixated)) @@ -487,7 +467,7 @@ Returns list of `phpinspect--completion'." :exit-function (lambda (comp-name state) (let ((comp (phpinspect--completion-list-get-metadata - phpinspect--last-completion-list + (phpi-comp-result-list result) comp-name))) (when (and (eq 'finished state) (eq 'function (phpinspect--completion-kind comp))) @@ -498,36 +478,33 @@ Returns list of `phpinspect--completion'." :company-kind (lambda (comp-name) (let ((comp (phpinspect--completion-list-get-metadata - phpinspect--last-completion-list + (phpi-comp-result-list result) comp-name))) (if comp (phpinspect--completion-kind comp) (phpinspect--log "Unable to find matching completion for name %s" comp-name) - nil)))))))) - -(defun phpinspect-complete-at-point () -(let* ((buf (current-buffer)) - result thread - (completion-ready nil) - (start (point)) - (end (point))) - - (setq thread (phpi-run-threaded "completion-at-point" - (with-current-buffer buf - (setq result (phpinspect--get-completion-at-point)) - (setq completion-ready t) - (message "set result")))) - - (message "STARTING WAIT") - (while (thread-live-p thread) - (message "waiting") - (sit-for 0.001)) - - (message "RESULT: %s" completion-ready) - (if completion-ready - (list start end result) - ;; Return a dummy completion table that will pass the try-completion test - (list start end '("") :exclusive 'no))) ) + nil))) + :exclusive (if (phpi-comp-result-strategy result) 'yes 'no))))) + +;; (defun phpinspect-complete-at-point () +;; (phpinspect--get-completion-at-point)) + +;; (setq thread (phpi-run-threaded "completion-at-point" +;; (with-current-buffer buf +;; (setq result +;; (setq completion-ready t) +;; (message "set result")))) + +;; (message "STARTING WAIT") +;; (while (thread-live-p thread) +;; (message "waiting") +;; (sit-for 0.001)) + +;; (message "RESULT: %s" completion-ready) +;; (if completion-ready +;; (list start end result) +;; ;; Return a dummy completion table that will pass the try-completion test +;; (list start end '("") :exclusive 'no))) ) ;; (let* ((buf (current-buffer)) ;; result thread) diff --git a/phpinspect-thread.el b/phpinspect-thread.el index 543b928e47..d80058aa9a 100644 --- a/phpinspect-thread.el +++ b/phpinspect-thread.el @@ -68,9 +68,9 @@ result)) -(defun phpi-make-condition (&optional value) +(defun phpi-make-condition (&optional value name) (let* ((mx (make-mutex)) - (condvar (make-condition-variable mx))) + (condvar (make-condition-variable mx name))) (phpi--make-condition :-mx mx :-condvar condvar :-value value))) (defvar phpinspect--main-thread-starving (phpi-make-condition 'no)) @@ -119,7 +119,7 @@ If current thread is the main thread, this function does nothing." (inline-quote (unless (eq main-thread (current-thread)) - (thread-yield)))) + (thread-yield)))) (defmacro phpi-progn (&rest body) `(prog1 @@ -133,7 +133,7 @@ If current thread is the main thread, this function does nothing." (defun phpi-start-job-queue (name job-handler) (declare (indent 1)) - (let* ((condition (phpi-make-condition)) + (let* ((condition (phpi-make-condition nil (format "%s condition" name))) queue) (setq queue (phpi--make-job-queue :subscription @@ -143,7 +143,7 @@ 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 (inhibit-quit t)) (catch 'phpi--break (while t @@ -155,14 +155,15 @@ If current thread is the main thread, this function does nothing." ;; If job queue end is signaled, exit after queue has ;; been fully depleted. (setq ended t) - (if (phpinspect-queue-first queue) - (setq ended nil) + (unless (phpinspect-queue-first queue) (throw 'phpi--break nil))))) - (if ended ;; End was signaled previously and the queue is empty. Exit. (throw 'phpi--break nil) - (phpi-condition-wait condition)))))))) + (progn + (setf (phpi-condition--value condition) + (phpinspect-queue-first queue)) + (phpi-condition-wait condition #'identity))))))))) queue)) (defun phpi-job-queue-live-p (queue) diff --git a/test/test-completion.el b/test/test-completion.el index 1fb1d3472b..775f8f3e5b 100644 --- a/test/test-completion.el +++ b/test/test-completion.el @@ -50,13 +50,13 @@ class Foo (let* ((query (phpinspect--get-completion-query)) result) - (setq result (phpinspect-completion-query-execute query)) + (setq result (phpi-comp-result-list (phpinspect-completion-query-execute query))) (should-not (phpinspect--completion-list-has-candidates result)) (insert "F") (setq query (phpinspect--get-completion-query) - result (phpinspect-completion-query-execute query)) + result (phpi-comp-result-list (phpinspect-completion-query-execute query))) (should-not (phpinspect--completion-list-has-candidates result))))) (ert-deftest phpinspect-complete-types-at-point () @@ -73,7 +73,7 @@ new ") (current-buffer) (phpinspect--make-dummy-composer-project-with-code)) (let* ((query (phpinspect--get-completion-query)) - (completions (phpinspect-completion-query-execute query)) + (completions (phpi-comp-result-list (phpinspect-completion-query-execute query))) (types (phpinspect--completion-list-strings completions))) (should (length= types 6)) @@ -97,7 +97,7 @@ public ") (let* ((query (phpinspect--get-completion-query)) (completions (phpinspect-completion-query-execute query)) - (strings (phpinspect--completion-list-strings completions))) + (strings (phpinspect--completion-list-strings (phpi-comp-result-list completions)))) (should strings)))) @@ -116,10 +116,9 @@ public function a() {} ") (current-buffer) (phpinspect--make-dummy-composer-project-with-code)) (let* ((query (phpinspect--get-completion-query)) - (completions (phpinspect-completion-query-execute query)) - (strings (phpinspect--completion-list-strings completions))) + (completions (phpinspect-completion-query-execute query))) - (should-not strings)))) + (should-not completions)))) (ert-deftest phpinspect-dont-complete-within-comments () (let ((cases (list "function a() { new // " @@ -140,8 +139,6 @@ class A { ") (current-buffer) (phpinspect--make-dummy-composer-project-with-code)) (let* ((query (phpinspect--get-completion-query)) - (completions (phpinspect-completion-query-execute query)) - (strings (phpinspect--completion-list-strings completions))) + (completions (phpinspect-completion-query-execute query))) - (should-not (phpinspect--completion-list-has-candidates completions)) - (should-not strings)))))) + (should-not completions))))))