-
-
Notifications
You must be signed in to change notification settings - Fork 123
/
org-db-fulltext.el
129 lines (101 loc) · 4.07 KB
/
org-db-fulltext.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
;;; org-db-fulltext.el --- Fulltext search for org-db
;;; Commentary:
;;
;; Add fulltext search for org-db. There are two main entry points.
;;
;; `org-db-fulltext-search' uses fts5 MATCH syntax for searching with dynamic
;; candidate completion. The candidates are short snippets of context.
;;
;; `org-db-fulltext-search-ivy' uses ivy on the full candidates, with ivy
;; matching. It is surprisingly fast. I like this one best.
(require 'org-db)
(defcustom org-db-fulltext "org-db-fulltext-v2.sqlite"
"Name of the sqlite database file for full-text search."
:type 'file
:group 'org-db)
(defun org-db-fulltext-setup ()
;; Make sure the database and table exists
(let ((org-db-ft (sqlite-open (expand-file-name org-db-fulltext org-db-root))))
(sqlite-execute org-db-ft "create virtual table if not exists fulltext using fts5(filename, contents)")
(sqlite-close org-db-ft)))
(defun org-db-fulltext-update (_filename-id _parse-tree _org-db)
"Insert the full text for searching.
This does not use the arguments pased in update functions."
(org-db-fulltext-setup)
(let ((org-db-ft (sqlite-open (expand-file-name org-db-fulltext org-db-root))))
(sqlite-execute org-db-ft "delete from fulltext where filename = ?"
(list (buffer-file-name)))
(sqlite-execute org-db-ft "insert into fulltext values (?, ?)"
(list (buffer-file-name)
(save-restriction
(widen)
(buffer-substring-no-properties (point-min) (point-max)))))
(sqlite-close org-db-ft)))
(add-to-list 'org-db-update-functions #'org-db-fulltext-update t)
(defun org-db-fulltext-candidates (query)
"Find candidates for fulltext search.
QUERY should be in fts5 syntax. We use MATCH for the select.
https://www.sqlite.org/fts5.html"
(or
(ivy-more-chars)
(let ((org-db-ft (sqlite-open (expand-file-name org-db-fulltext org-db-root)))
(statement (format "select snippet(fulltext, 1, '', '', '', 8), filename
from fulltext where contents match '%s'%s"
query
(if org-db-project-p
(format " and filename match '\"^%s\"'" (projectile-project-root))
""))))
(prog1
(cl-loop for (snippet fname) in
(sqlite-select org-db-ft statement)
;; I don't love this, but for some reason in the ivy command,
;; I don't get a cons result, so this is the only way I know
;; to get the filename. Its probably not even reliable because
;; the full text might have :: in it.
collect
;; (cons snippet fname)
(format "%s :: %s" snippet fname))
(sqlite-close org-db-ft)))))
(defun org-db-fulltext-open (cand)
"Open the file in CAND."
(cl-destructuring-bind (snippet fname)
(mapcar 'string-trim (split-string cand "::"))
(find-file fname)
(goto-char (point-min))
(search-forward snippet nil t)))
(defvar org-db-project-p nil
"Boolean for limiting search to current project.")
(defun org-db-fulltext-search (&optional project)
"Search the fulltext database.
This function opens the file that matches your query string.
With optional prefix arg PROJECT limit query to current project."
(interactive "P")
(setq org-db-project-p project)
(ivy-read "query: " #'org-db-fulltext-candidates
:dynamic-collection t
:action #'org-db-fulltext-open))
(defun org-db-fulltext-search-ivy ()
"Search the fulltext database.
This function opens the file that matches your query string."
(interactive)
;; this uses ivy for completion.
(let ((candidates (let ((org-db-ft (sqlite-open (expand-file-name org-db-fulltext org-db-root))))
(prog1
(sqlite-select org-db-ft "select contents, filename from fulltext")
(sqlite-close org-db-ft)))))
(ivy-read "query: " candidates
:action #'org-db-fulltext-open)))
(defun org-db-ft-transformer (s)
"This only shows lines that match the selected pattern."
(s-join "\n"
(cl-loop for x in (split-string s "\n")
if (string-match-p ivy-text x)
collect x)))
(ivy-set-display-transformer
'org-db-fulltext-search-ivy
'org-db-ft-transformer)
(provide 'org-db-fulltext)
;;; org-db-fulltext.el ends here
;; Local Variables:
;; eval: (sem-mode)
;; End: