Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up auto-completion when using company #187

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 98 additions & 29 deletions ejc-company.el
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
;;; ejc-company.el -- SQL completitions at point by company-mode (the part of ejc-sql).
;;; ejc-company.el -- SQL completitions at point by company-mode (the part of ejc-sql). -*- lexical-binding: t -*-

;;; Copyright © 2020 - Kostafey <[email protected]>

Expand Down Expand Up @@ -31,6 +31,25 @@
(require 'company)
(require 'ejc-completion-common)

(defcustom ejc-company-cache-update-ivl-secs 60
"Specify how often to update cached candidates in seconds.
If set to 1.0e+INF, do not update cache after initialization."
:type 'integer :group 'ejc-sql)

(defcustom ejc-company-idle-timer-secs 1
"Collect candidates after specified amount of idleness in seconds."
:type 'integer :group 'ejc-sql)

(defvar-local ejc-company--candidates nil
"Cached candidates.")

(defvar-local ejc-company--cache-update-ts nil
"Last timestamp of cache update.")

(defvar-local ejc-company--cache-update-scheduled nil
"Whether `ejc-company--cache-candidates' is already scheduled with `run-with-idle-timer'.")


(defun ejc-company-make-candidate (candidate)
(let ((text (car candidate))
(meta (cadr candidate)))
Expand All @@ -40,35 +59,85 @@
(-map (lambda (k) (list k meta))
candidates))

(defun ejc-company--collect-all-candidates (&optional on-point)
(append
(ejc-append-without-duplicates
(ejc-company-add-meta
"ansi sql" (ejc-get-ansi-sql-words))
(ejc-company-add-meta
"keyword" (ejc-get-keywords))
'car :right)
(ejc-company-add-meta
"owner" (ejc-owners-candidates))
(ejc-company-add-meta
"table" (ejc-tables-candidates))
(ejc-company-add-meta
"view" (ejc-views-candidates))
(when (not on-point)
(ejc-company-add-meta
"package" (ejc-packages-candidates)))
(when on-point
(ejc-company-add-meta
"column" (ejc-colomns-candidates)))))

(defun ejc-company-make-candidates (prefix items)
"Filter `ITEMS' that are not started with `PREFIX' and prepare them for company."
(mapcar #'ejc-company-make-candidate
(if (string= "" prefix)
items
(cl-remove-if-not
(lambda (c) (string-prefix-p prefix (car c) t))
items))))

(defun ejc-company--cache-candidates (buffer)
"Collect candidates for `BUFFER' and put them into a buffer-local variable."
(when (buffer-live-p buffer)
(with-current-buffer buffer
(save-excursion
;; we have to collect keywords, table/view names etc.
;; Since this function is invoked by timer we can't guarantee that point is not on a dot (e.g. var.|).
;; All collect functions will check buffer position and behave differently if point is on the dot.
;; So we should make sure not to be on the dot.
;; The simplest is just go to the beginning of a buffer
(goto-char (point-min))
(condition-case err-cons
(setq ejc-company--candidates (ejc-company--collect-all-candidates))
(error (message (cadr err-cons))))
(setq ejc-company--cache-update-ts (float-time)
ejc-company--cache-update-scheduled nil)))))

(defun ejc-company--schedule-cache-update ()
"Schedule cache update if cache is empty or it was updated too long ago."
(when (and (not ejc-company--cache-update-scheduled)
(or (not ejc-company--candidates)
(not ejc-company--cache-update-ts)
(> (- (float-time) ejc-company--cache-update-ts)
ejc-company-cache-update-ivl-secs)))
(run-with-idle-timer ejc-company-idle-timer-secs
nil
#'ejc-company--cache-candidates (current-buffer))
(setq ejc-company--cache-update-scheduled t)))

(defun ejc-company-candidates (prefix)
(let* ((prefix-1 (ejc-get-prefix-word))
(prefix-2 (save-excursion
(search-backward "." nil t)
(ejc-get-prefix-word)))
(res))
(dolist (item
(cl-remove-if-not
(lambda (c) (string-prefix-p prefix (car c) t))
(append
(ejc-append-without-duplicates
(ejc-company-add-meta
"ansi sql" (ejc-get-ansi-sql-words))
(ejc-company-add-meta
"keyword" (ejc-get-keywords))
'car :right)
(ejc-company-add-meta
"owner" (ejc-owners-candidates))
(ejc-company-add-meta
"table" (ejc-tables-candidates))
(ejc-company-add-meta
"view" (ejc-views-candidates))
(if (not prefix-1)
(ejc-company-add-meta
"package" (ejc-packages-candidates)))
(ejc-company-add-meta
"column" (ejc-colomns-candidates)))))
(push (ejc-company-make-candidate item) res))
res))
"If the point is on dot (name.| or name.var|) then synchronously collect candidates.
Otherwise use cached data. When cache is empty (first time invocation),
it returns only common SQL words and schedules the cache update.
`PREFIX' is used for filtering candidates."


(let* ((on-point (ejc-get-prefix-word)))
(if on-point
(ejc-company-make-candidates prefix (ejc-company--collect-all-candidates t))

(ejc-company--schedule-cache-update)
(if ejc-company--candidates
(ejc-company-make-candidates prefix ejc-company--candidates)

;; first time invocation, return only sql words
(ejc-company-make-candidates prefix
(setq ejc-company--candidates
(ejc-company-add-meta
"ansi sql" (ejc-get-ansi-sql-words))))))))

(defun ejc-company-annotation (candidate)
(format " %s" (get-text-property 0 'meta candidate)))
Expand Down
11 changes: 3 additions & 8 deletions ejc-completion-common.el
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,9 @@ Uppercase by default, set to nil to use downcase candidates."

(defun ejc-return-point ()
"Return point position if point (cursor) is located next to dot char (.#)"
(let ((curr-char (buffer-substring
(save-excursion
(left-char 1)
(point))
(point))))
(if (equal curr-char ".")
(point)
nil)))
(if (= ?. (or (char-before) 0))
(point)
nil))

(defun ejc-get-prefix-word ()
"Return the word preceding dot before the typing."
Expand Down