branch: externals/company
commit 374322ceac9bf387c539d0ab047e0a9bb87ce181
Author: Dmitry Gutov <dmi...@gutov.dev>
Commit: Dmitry Gutov <dmi...@gutov.dev>

    Add caching helpers and use them in company-dabbrev and company-ispell
    
    Previously described in #285.
---
 company-dabbrev.el | 33 +++++++++++++++++++--------------
 company-ispell.el  | 36 ++++++++++++++++++++++--------------
 company.el         | 31 +++++++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 28 deletions(-)

diff --git a/company-dabbrev.el b/company-dabbrev.el
index b7434de393..d8a3d0ede0 100644
--- a/company-dabbrev.el
+++ b/company-dabbrev.el
@@ -1,6 +1,6 @@
 ;;; company-dabbrev.el --- dabbrev-like company-mode completion backend  -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2009-2011, 2013-2018, 2021  Free Software Foundation, Inc.
+;; Copyright (C) 2009-2011, 2013-2018, 2021-2023  Free Software Foundation, 
Inc.
 
 ;; Author: Nikolaj Schumacher
 
@@ -180,6 +180,19 @@ This variable affects both `company-dabbrev' and 
`company-dabbrev-code'."
   (let ((completion-ignore-case company-dabbrev-ignore-case))
     (all-completions prefix candidates)))
 
+(defun company-dabbrev--fetch ()
+  (let ((words (company-dabbrev--search (company-dabbrev--make-regexp)
+                                        company-dabbrev-time-limit
+                                        (pcase company-dabbrev-other-buffers
+                                          (`t (list major-mode))
+                                          (`all `all))))
+        (downcase-p (if (eq company-dabbrev-downcase 'case-replace)
+                        case-replace
+                      company-dabbrev-downcase)))
+    (if downcase-p
+        (mapcar 'downcase words)
+      words)))
+
 ;;;###autoload
 (defun company-dabbrev (command &optional arg &rest _ignored)
   "dabbrev-like `company-mode' completion backend."
@@ -188,20 +201,12 @@ This variable affects both `company-dabbrev' and 
`company-dabbrev-code'."
     (interactive (company-begin-backend 'company-dabbrev))
     (prefix (company-dabbrev--prefix))
     (candidates
-     (let* ((case-fold-search company-dabbrev-ignore-case)
-            (words (company-dabbrev--search (company-dabbrev--make-regexp)
-                                            company-dabbrev-time-limit
-                                            (pcase 
company-dabbrev-other-buffers
-                                              (`t (list major-mode))
-                                              (`all `all))))
-            (downcase-p (if (eq company-dabbrev-downcase 'case-replace)
-                            case-replace
-                          company-dabbrev-downcase)))
-       (setq words (company-dabbrev--filter arg words))
-       (if downcase-p
-           (mapcar 'downcase words)
-         words)))
+     (company-dabbrev--filter
+      arg
+      (company-cache-fetch 'dabbrev-candidates #'company-dabbrev--fetch
+                           :expire t)))
     (kind 'text)
+    (no-cache t)
     (ignore-case company-dabbrev-ignore-case)
     (duplicates t)))
 
diff --git a/company-ispell.el b/company-ispell.el
index 3cb7c5d693..7c207f3eea 100644
--- a/company-ispell.el
+++ b/company-ispell.el
@@ -1,6 +1,6 @@
 ;;; company-ispell.el --- company-mode completion backend using Ispell
 
-;; Copyright (C) 2009-2011, 2013-2016, 2018, 2021  Free Software Foundation, 
Inc.
+;; Copyright (C) 2009-2011, 2013-2016, 2018, 2021, 2023  Free Software 
Foundation, Inc.
 
 ;; Author: Nikolaj Schumacher
 
@@ -33,30 +33,35 @@
   "Completion backend using Ispell."
   :group 'company)
 
+(defun company--set-dictionary (symbol value)
+  (set-default-toplevel-value symbol value)
+  (company-cache-delete 'ispell-candidates))
+
 (defcustom company-ispell-dictionary nil
   "Dictionary to use for `company-ispell'.
 If nil, use `ispell-complete-word-dict'."
   :type '(choice (const :tag "default (nil)" nil)
-                 (file :tag "dictionary" t)))
+                 (file :tag "dictionary" t))
+  :set #'company--set-dictionary)
 
 (defvar company-ispell-available 'unknown)
 
-(defalias 'company-ispell--lookup-words
-  (if (fboundp 'ispell-lookup-words)
-      'ispell-lookup-words
-    'lookup-words))
-
 (defun company-ispell-available ()
   (when (eq company-ispell-available 'unknown)
     (condition-case err
         (progn
-          (company-ispell--lookup-words "WHATEVER")
+          (ispell-lookup-words "WHATEVER")
           (setq company-ispell-available t))
       (error
        (message "Company-Ispell: %s" (error-message-string err))
        (setq company-ispell-available nil))))
   company-ispell-available)
 
+(defun company--ispell-dict ()
+  (or company-ispell-dictionary
+      ispell-complete-word-dict
+      ispell-alternate-dictionary))
+
 ;;;###autoload
 (defun company-ispell (command &optional arg &rest ignored)
   "`company-mode' completion backend using Ispell."
@@ -66,16 +71,19 @@ If nil, use `ispell-complete-word-dict'."
     (prefix (when (company-ispell-available)
               (company-grab-word)))
     (candidates
-     (let ((words (company-ispell--lookup-words
-                   arg
-                   (or company-ispell-dictionary ispell-complete-word-dict)))
-           (completion-ignore-case t))
+     (let* ((dict (company--ispell-dict))
+            (all-words
+             (company-cache-fetch 'ispell-candidates
+                                  (lambda () (ispell-lookup-words "" dict))
+                                  :check-tag dict))
+            (completion-ignore-case t))
        (if (string= arg "")
            ;; Small optimization.
-           words
+           all-words
          ;; Work around issue #284.
-         (all-completions arg words))))
+         (all-completions arg all-words))))
     (kind 'text)
+    (no-cache t)
     (sorted t)
     (ignore-case 'keep-prefix)))
 
diff --git a/company.el b/company.el
index a0d8dc086c..2ead4e4864 100644
--- a/company.el
+++ b/company.el
@@ -1133,6 +1133,33 @@ matches IDLE-BEGIN-AFTER-RE, return it wrapped in a 
cons."
         (car (setq ppss (cdr ppss)))
         (nth 3 ppss))))
 
+(defvar company--cache (make-hash-table :test #'equal :size 10))
+
+(cl-defun company-cache-fetch (key
+                               fetcher
+                               &key expire &key check-tag)
+  "Fetch the value assigned to KEY in the cache.
+When not found, or when found to be stale, calls FETCHER to compute the
+result.  When EXPIRE is non-nil, the value will be deleted at the end of
+completion.  CHECK-TAG, when present, is saved as well, and the entry will
+be recomputed when this value changes."
+  ;; We could make EXPIRE accept a time value as well.
+  (let ((res (gethash key company--cache 'none))
+        value)
+    (if (and (not (eq res 'none))
+             (or (not check-tag)
+                 (equal check-tag (assoc-default :check-tag res))))
+        (assoc-default :value res)
+      (setq res (list (cons :value (setq value (funcall fetcher)))))
+      (if expire (push '(:expire . t) res))
+      (if check-tag (push `(:check-tag . ,check-tag) res))
+      (puthash key res company--cache)
+      value)))
+
+(defun company-cache-delete (key)
+  "Delete KEY from cache."
+  (remhash key company--cache))
+
 (defun company-call-backend (&rest args)
   (company--force-sync #'company-call-backend-raw args company-backend))
 
@@ -2204,6 +2231,10 @@ For more details see `company-insertion-on-trigger' and
           company--multi-uncached-backends nil
           company--multi-min-prefix nil
           company-point nil)
+    (maphash (lambda (k v)
+               (when (assoc-default :expire v)
+                 (remhash k company--cache)))
+             company--cache)
     (when company-timer
       (cancel-timer company-timer))
     (company-echo-cancel t)

Reply via email to