org-roam/company-org-roam.el
Jethro Kuan 6095d01ef4
(feat): add a company-mode for link completion (#257)
Adds company-org-roam, a company backend which autocompletes text into org file links
2020-03-11 14:32:58 +08:00

139 lines
4.9 KiB
EmacsLisp

;;; company-org-roam.el --- Company backend for Org-roam
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.0.0-rc1
;; Package-Requires: ((emacs "26.1") (company "0.9.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; `company-org-roam' is a `company' completion backend for Org-roam.
;; To use it, add `company-org-roam' to `company-backends':
;; (require 'company-org-roam)
;; (company-org-roam-init)
;;; Code:
(require 'cl-lib)
(require 'company)
(require 'org-roam)
(defgroup company-org-roam nil
"Company completion backend for Org-roam."
:prefix "company-org-roam-"
:group 'org-roam)
(defcustom company-org-roam-cache-expire 10
"Number of seconds before the caches expire.
A value of nil means the caches never expire."
:type '(integer :tag "Seconds")
:group 'company-org-roam)
(defvar company-org-roam-last-cache-time nil
"Time of last cache.")
(defvar company-org-roam-cache nil
"In-memory cache for completions.")
(defun company-org-roam-time-seconds ()
"Return the number of seconds since the unix epoch."
(cl-destructuring-bind (high low _usec _psec) (current-time)
(+ (lsh high 16) low)))
(defun company-org-roam--post-completion (candidate)
"The post-completion action for `company-org-roam'.
It deletes the inserted CANDIDATE, and replaces it with a
relative file link."
(let* ((path (gethash candidate company-org-roam-cache))
(current-file-path (-> (or (buffer-base-buffer)
(current-buffer))
(buffer-file-name)
(file-truename)
(file-name-directory))))
(delete-region (- (point) (length candidate)) (point))
(insert (format "[[file:%s][%s]]"
(file-relative-name path current-file-path)
candidate))))
(defun company-org-roam--filter-candidates (prefix candidates)
"Filter CANDIDATES that start with PREFIX.
The string match is case-insensitive."
(-filter (lambda (candidate)
(s-starts-with-p prefix candidate t)) candidates))
(defun company-org-roam--update-cache ()
"Update the cache with new entries.
Entries with no title do not appear in the completions."
(setq company-org-roam-cache (make-hash-table :test #'equal))
(dolist (row (org-roam-sql [:select [titles file] :from titles]))
(let ((titles (car row))
(file (cadr row)))
(dolist (title titles)
(puthash title file company-org-roam-cache)))))
(defun company-org-roam--cache-get-titles ()
"Return all the titles."
(when (or (null company-org-roam-last-cache-time)
(< (+ company-org-roam-last-cache-time company-org-roam-cache-expire)
(company-org-roam-time-seconds)))
(setq company-org-roam-last-cache-time (company-org-roam-time-seconds))
(company-org-roam--update-cache))
(hash-table-keys company-org-roam-cache))
(defun company-org-roam--get-candidates (prefix)
"Get the candidates for PREFIX."
(->> (company-org-roam--cache-get-titles)
(-flatten)
(company-org-roam--filter-candidates prefix)))
;;;###autoload
(defun company-org-roam (command &optional arg &rest _)
"Define a company backend for Org-roam.
COMMAND and ARG are as per the documentation of `company-backends'."
(interactive (list 'interactive))
(cl-case command
(interactive (company-begin-backend #'company-org-roam))
(prefix
(and
(bound-and-true-p org-roam-mode)
(or (company-grab-symbol) 'stop)))
(candidates
(company-org-roam--get-candidates arg))
(post-completion (company-org-roam--post-completion arg))))
(defun company-org-roam--init-hook ()
"Conditional enabling of the `company-org-roam' backend."
(when (org-roam--org-roam-file-p (buffer-file-name (buffer-base-buffer)))
(setq-local company-backends
(cons'company-org-roam company-backends))))
;;;###autoload
(defun company-org-roam-init ()
"Injects `company-org-roam' as a completion backend."
(add-hook 'org-mode-hook #'company-org-roam--init-hook))
(provide 'company-org-roam)
;;; company-org-roam.el ends here