org-velocity.el 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. ;;; org-velocity.el --- something like Notational Velocity for Org.
  2. ;; Copyright (C) 2010, 2011 Paul M. Rodriguez
  3. ;; Author: Paul M. Rodriguez <paulmrodriguez@gmail.com>
  4. ;; Created: 2010-05-05
  5. ;; Version: 2.4
  6. ;; This file is not part of GNU Emacs.
  7. ;; This program is free software; you can redistribute it and/or
  8. ;; modify it under the terms of the GNU General Public License as
  9. ;; published by the Free Software Foundation version 2.
  10. ;; This program is distributed in the hope that it will be useful, but
  11. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. ;; General Public License for more details.
  14. ;; For a copy of the GNU General Public License, search the Internet,
  15. ;; or write to the Free Software Foundation, Inc., 59 Temple Place,
  16. ;; Suite 330, Boston, MA 02111-1307 USA
  17. ;;; Commentary:
  18. ;; Org-Velocity.el is an interface for Org inspired by the minimalist
  19. ;; notetaking program Notational Velocity. The idea is to let you
  20. ;; amass and access brief notes on many subjects with minimal fuss.
  21. ;; Each note is an entry in an ordinary Org file.
  22. ;; Org-Velocity can be used in two ways: when called outside Org, to
  23. ;; store and access notes in a designated bucket file; or, when called
  24. ;; inside Org, as a method for navigating any Org file. (Setting the
  25. ;; option `org-velocity-always-use-bucket' disables navigation inside
  26. ;; Org files by default, although you can still force this behavior by
  27. ;; calling `org-velocity-read' with an argument.)
  28. ;; Org-Velocity prompts for search terms in the minibuffer. A list of
  29. ;; headings of entries whose text matches your search is updated as
  30. ;; you type; you can end the search and visit an entry at any time by
  31. ;; clicking on its heading.
  32. ;; RET displays the results. If there are no matches, Org-Velocity
  33. ;; offers to create a new entry with your search string as its
  34. ;; heading. If there are matches, it displays a list of results where
  35. ;; the heading of each matching entry is hinted with a number or
  36. ;; letter; clicking a result, or typing the matching hint, opens the
  37. ;; entry for editing in an indirect buffer. 0 forces a new entry; RET
  38. ;; reopens the search for editing.
  39. ;; You can customize every step in this process, including the search
  40. ;; method, completion for search terms, and templates for creating new
  41. ;; entries; M-x customize-group RET org-velocity RET to see all the
  42. ;; options.
  43. ;; Thanks to Richard Riley, Carsten Dominik, Bastien Guerry, and Jeff
  44. ;; Horn for their suggestions.
  45. ;;; Usage:
  46. ;; (require 'org-velocity)
  47. ;; (setq org-velocity-bucket (expand-file-name "bucket.org" org-directory))
  48. ;; (global-set-key (kbd "C-c v") 'org-velocity-read)
  49. ;;; Code:
  50. (require 'org)
  51. (require 'button)
  52. (require 'electric)
  53. (require 'dabbrev)
  54. (eval-when-compile (require 'cl))
  55. (defgroup org-velocity nil
  56. "Notational Velocity-style interface for Org."
  57. :tag "Org-Velocity"
  58. :group 'outlines
  59. :group 'hypermedia
  60. :group 'org)
  61. (defcustom org-velocity-bucket ""
  62. "Where is the bucket file?"
  63. :group 'org-velocity
  64. :type 'file)
  65. (defcustom org-velocity-search-is-incremental t
  66. "Show results incrementally when possible?"
  67. :group 'org-velocity
  68. :type 'boolean
  69. :safe 'booleanp)
  70. (defcustom org-velocity-exit-on-match nil
  71. "When searching incrementally, exit on a single match?"
  72. :group 'org-velocity
  73. :type 'boolean
  74. :safe 'booleanp)
  75. (defcustom org-velocity-force-new nil
  76. "Should exiting the minibuffer with C-j force a new entry?"
  77. :group 'org-velocity
  78. :type 'boolean
  79. :safe 'booleanp)
  80. (defcustom org-velocity-max-depth nil
  81. "Ignore headings deeper than this."
  82. :group 'org-velocity
  83. :type '(choice
  84. (const :tag "No maximum depth" nil)
  85. (integer :tag "Set maximum depth"))
  86. :safe (lambda (v) (or (null v) (wholenump v))))
  87. (defcustom org-velocity-use-search-ring t
  88. "Push search to `search-ring' when visiting an entry?
  89. This means that C-s C-s will take you directly to the first
  90. instance of the search string."
  91. :group 'org-velocity
  92. :type 'boolean
  93. :safe 'booleanp)
  94. (defcustom org-velocity-always-use-bucket nil
  95. "Use bucket file even when called from an Org buffer?"
  96. :group 'org-velocity
  97. :type 'boolean
  98. :safe 'booleanp)
  99. (defcustom org-velocity-use-completion nil
  100. "Use completion?
  101. Notwithstanding the value of this option, calling
  102. `dabbrev-expand' always completes against the text of the bucket
  103. file."
  104. :group 'org-velocity
  105. :type '(choice
  106. (const :tag "Do not use completion" nil)
  107. (const :tag "Use completion" t))
  108. :safe 'booleanp)
  109. (defcustom org-velocity-edit-indirectly t
  110. "Edit entries in an indirect buffer or just visit the file?"
  111. :group 'org-velocity
  112. :type 'boolean
  113. :safe 'booleanp)
  114. (defcustom org-velocity-search-method 'phrase
  115. "Match on whole phrase, any word, or all words?"
  116. :group 'org-velocity
  117. :type '(choice
  118. (const :tag "Match whole phrase" phrase)
  119. (const :tag "Match any word" any)
  120. (const :tag "Match all words" all)
  121. (const :tag "Match a regular expression" regexp))
  122. :safe (lambda (v) (memq v '(phrase any all regexp))))
  123. (defcustom org-velocity-create-method 'capture
  124. "Prefer `org-capture', `org-remember', or neither?"
  125. :group 'org-velocity
  126. :type '(choice
  127. (const :tag "Prefer capture > remember > default." capture)
  128. (const :tag "Prefer remember > default." remember)
  129. (const :tag "Edit in buffer." buffer))
  130. :safe (lambda (v) (memq v '(capture remember buffer))))
  131. (defcustom org-velocity-remember-templates
  132. '(("Velocity entry"
  133. ?v
  134. "* %:search\n\n%i%?"
  135. nil
  136. bottom))
  137. "Use these templates with `org-remember'.
  138. Meanwhile `org-default-notes-file' is bound to `org-velocity-use-file'.
  139. The keyword :search inserts the current search.
  140. See the documentation for `org-remember-templates'."
  141. :group 'org-velocity
  142. :type (or (get 'org-remember-templates 'custom-type) 'list))
  143. (defcustom org-velocity-capture-templates
  144. '(("v"
  145. "Velocity entry"
  146. entry
  147. (file "")
  148. "* %:search\n\n%i%?"))
  149. "Use these template with `org-capture'.
  150. Meanwhile `org-default-notes-file' is bound to `org-velocity-use-file'.
  151. The keyword :search inserts the current search.
  152. See the documentation for `org-capture-templates'."
  153. :group 'org-velocity
  154. :type (or (get 'org-capture-templates 'custom-type) 'list))
  155. (defstruct (org-velocity-heading
  156. (:constructor org-velocity-make-heading
  157. (&aux (components (org-heading-components))))
  158. (:type list))
  159. (marker (point-marker))
  160. (name (nth 4 components))
  161. (level (nth 0 components)))
  162. (defconst org-velocity-index
  163. (eval-when-compile
  164. (nconc (number-sequence 49 57) ;numbers
  165. (number-sequence 97 122) ;lowercase letters
  166. (number-sequence 65 90))) ;uppercase letters
  167. "List of chars for indexing results.")
  168. (defconst org-velocity-display-buffer-name "*Velocity headings*")
  169. (defvar org-velocity-search nil
  170. "Variable to bind to current search.")
  171. (defsubst org-velocity-buffer-file-name (&optional buffer)
  172. "Return the name of the file BUFFER saves to.
  173. Same as function `buffer-file-name' unless BUFFER is an indirect
  174. buffer or a minibuffer. In the former case, return the file name
  175. of the base buffer; in the latter, return the file name of
  176. `minibuffer-selected-window' (or its base buffer)."
  177. (let ((buffer (if (minibufferp buffer)
  178. (window-buffer (minibuffer-selected-window))
  179. buffer)))
  180. (buffer-file-name
  181. (or (buffer-base-buffer buffer)
  182. buffer))))
  183. (defun org-velocity-minibuffer-contents ()
  184. "Return the contents of the minibuffer when it is active."
  185. (if (active-minibuffer-window)
  186. (with-current-buffer (window-buffer (active-minibuffer-window))
  187. (minibuffer-contents))))
  188. (defun org-velocity-use-file ()
  189. "Return the proper file for Org-Velocity to search.
  190. If `org-velocity-always-use-bucket' is t, use bucket file; complain
  191. if missing. Otherwise if this is an Org file, use it."
  192. (or
  193. ;; Use the target in in remember buffers.
  194. (if (and (boundp 'org-remember-mode) org-remember-mode)
  195. org-default-notes-file)
  196. (let ((org-velocity-bucket
  197. (and org-velocity-bucket (expand-file-name org-velocity-bucket)))
  198. (buffer (if (org-velocity-buffer-file-name)
  199. ;; Use the target in capture buffers.
  200. (org-find-base-buffer-visiting (org-velocity-buffer-file-name)))))
  201. (if org-velocity-always-use-bucket
  202. (or org-velocity-bucket (error "Bucket required but not defined"))
  203. (if (and (eq (buffer-local-value 'major-mode (or buffer (current-buffer)))
  204. 'org-mode)
  205. (org-velocity-buffer-file-name))
  206. (org-velocity-buffer-file-name)
  207. (or org-velocity-bucket
  208. (error "No bucket and not an Org file")))))))
  209. (defsubst org-velocity-display-buffer ()
  210. "Return the proper buffer for Org-Velocity to display in."
  211. (get-buffer-create org-velocity-display-buffer-name))
  212. (defsubst org-velocity-bucket-buffer ()
  213. "Return proper buffer for bucket operations."
  214. (find-file-noselect (org-velocity-use-file)))
  215. (defun org-velocity-nearest-heading (position)
  216. "Return last heading at POSITION.
  217. If there is no last heading, return nil."
  218. (save-excursion
  219. (goto-char position)
  220. ;; If we are before the first heading we could still be at the
  221. ;; first heading.
  222. (unless (and (org-before-first-heading-p)
  223. (not (org-at-heading-p)))
  224. (org-back-to-heading t)
  225. (let ((heading (org-velocity-make-heading)))
  226. (if org-velocity-max-depth
  227. (if (<= (org-velocity-heading-level heading)
  228. org-velocity-max-depth)
  229. heading)
  230. heading)))))
  231. (defun org-velocity-make-button-action (heading)
  232. "Return a form to visit HEADING."
  233. `(lambda (button)
  234. (run-hooks 'mouse-leave-buffer-hook) ;turn off temporary modes
  235. (if org-velocity-use-search-ring
  236. (add-to-history 'search-ring ,org-velocity-search search-ring-max))
  237. (if org-velocity-edit-indirectly
  238. (org-velocity-edit-entry ',heading)
  239. (progn
  240. (message "%s" ,(org-velocity-heading-name heading))
  241. (switch-to-buffer (marker-buffer
  242. ,(org-velocity-heading-marker heading)))
  243. (goto-char (marker-position
  244. ,(org-velocity-heading-marker heading)))))))
  245. (defun org-velocity-make-indirect-buffer (heading)
  246. "Make or switch to an indirect buffer visiting HEADING."
  247. (let* ((bucket (marker-buffer (org-velocity-heading-marker heading)))
  248. (name (org-velocity-heading-name heading))
  249. (existing (get-buffer name)))
  250. (if (and existing (buffer-base-buffer existing)
  251. (equal (buffer-base-buffer existing) bucket))
  252. existing
  253. (make-indirect-buffer
  254. bucket
  255. (generate-new-buffer-name (org-velocity-heading-name heading))))))
  256. (defun org-velocity-edit-entry (heading)
  257. "Edit entry at HEADING in an indirect buffer."
  258. (let ((buffer (org-velocity-make-indirect-buffer heading)))
  259. (with-current-buffer buffer
  260. (let ((org-inhibit-startup t))
  261. (org-mode))
  262. (goto-char (marker-position (org-velocity-heading-marker heading)))
  263. (narrow-to-region (point)
  264. (save-excursion
  265. (org-end-of-subtree t)
  266. (point)))
  267. (goto-char (point-min))
  268. (add-hook 'org-ctrl-c-ctrl-c-hook 'org-velocity-dismiss nil t))
  269. (pop-to-buffer buffer)
  270. (set (make-local-variable 'header-line-format)
  271. (format "%s Use C-c C-c to finish."
  272. (abbreviate-file-name
  273. (buffer-file-name
  274. (marker-buffer
  275. (org-velocity-heading-marker heading))))))))
  276. (defun org-velocity-dismiss ()
  277. "Save current entry and close indirect buffer."
  278. (progn
  279. (save-buffer)
  280. (kill-buffer)))
  281. (defun org-velocity-buttonize-no-hints (heading)
  282. "Insert HEADING as a text button with no hints."
  283. (let ((action (org-velocity-make-button-action heading)))
  284. (insert-text-button
  285. (org-velocity-heading-name heading)
  286. 'action action))
  287. (newline))
  288. (defun org-velocity-buttonize (heading)
  289. "Insert HEADING as a text button with an hint."
  290. (insert (format "#%c " (nth (1- (line-number-at-pos))
  291. org-velocity-index)))
  292. (org-velocity-buttonize-no-hints heading))
  293. (defun org-velocity-remember ()
  294. "Use `org-remember' to record a note."
  295. (let ((org-remember-templates
  296. org-velocity-remember-templates))
  297. (call-interactively 'org-remember)
  298. (when org-remember-mode
  299. (set (make-local-variable 'remember-buffer)
  300. (rename-buffer org-velocity-search t)))))
  301. (defun org-velocity-capture ()
  302. "Use `org-capture' to record a note."
  303. (let ((org-capture-templates
  304. org-velocity-capture-templates))
  305. (when (fboundp 'org-capture) ;; quiet compiler
  306. (call-interactively 'org-capture)
  307. (if org-capture-mode (rename-buffer org-velocity-search t)))))
  308. (defun org-velocity-insert-heading (&optional heading)
  309. "Add a new heading named HEADING and go to it."
  310. (let ((heading (or heading org-velocity-search)))
  311. (pop-to-buffer (org-velocity-bucket-buffer))
  312. (goto-char (point-max))
  313. (let ((inhibit-quit t))
  314. (newline)
  315. (org-insert-heading t t) (insert heading)
  316. (newline)
  317. (goto-char (point-max)))))
  318. (defun org-velocity-generic-search (search)
  319. "Return entries containing SEARCH."
  320. (save-excursion
  321. (loop initially (goto-char (point-min))
  322. while (re-search-forward search (point-max) t)
  323. if (org-velocity-nearest-heading (match-beginning 0))
  324. collect it
  325. do (outline-next-heading))))
  326. (defsubst org-velocity-phrase-search (search)
  327. "Return entries containing SEARCH as a phrase."
  328. (org-velocity-generic-search (regexp-quote search)))
  329. (defsubst org-velocity-any-search (search)
  330. "Return entries containing any word in SEARCH."
  331. (org-velocity-generic-search (regexp-opt (split-string search))))
  332. (defsubst org-velocity-regexp-search (search)
  333. (condition-case lossage
  334. (org-velocity-generic-search search)
  335. (invalid-regexp (minibuffer-message "%s" lossage))))
  336. (defun org-velocity-all-search (search)
  337. "Return entries containing all words in SEARCH."
  338. (save-excursion
  339. (let ((keywords (mapcar 'regexp-quote (split-string search))))
  340. (delq nil
  341. (org-map-entries
  342. (lambda ()
  343. ;; Only search the subtree once.
  344. (setq org-map-continue-from
  345. (save-excursion (org-end-of-subtree t) (point)))
  346. (if (loop for word in keywords
  347. always (save-excursion
  348. (re-search-forward
  349. word org-map-continue-from t)))
  350. (org-velocity-nearest-heading (point)))))))))
  351. (defun org-velocity-present (headings &optional no-hints search)
  352. "Buttonize HEADINGS in `org-velocity-display-buffer'.
  353. If NO-HINTS is non-nil, display entries without indices.
  354. SEARCH binds `org-velocity-search'."
  355. (and (listp headings) (delete-dups headings))
  356. (let ((cdr (nthcdr
  357. (1- (length org-velocity-index))
  358. headings)))
  359. (and (consp cdr) (setcdr cdr nil)))
  360. (let ((org-velocity-search search))
  361. (with-current-buffer (org-velocity-display-buffer)
  362. (mapc
  363. (if no-hints 'org-velocity-buttonize-no-hints
  364. 'org-velocity-buttonize)
  365. headings)
  366. (goto-char (point-min)))))
  367. (defun org-velocity-create-1 ()
  368. "Create a new heading.
  369. The possible methods are `org-velocity-capture',
  370. `org-velocity-remember', or `org-velocity-create', in
  371. that order. Which is preferred is determined by
  372. `org-velocity-create-method'."
  373. (funcall
  374. (ecase org-velocity-create-method
  375. (capture (or (and (featurep 'org-capture) 'org-velocity-capture)
  376. (and (featurep 'org-remember) 'org-velocity-remember)
  377. 'org-velocity-insert-heading))
  378. (remember (or (and (featurep 'org-remember) 'org-velocity-remember)
  379. 'org-velocity-insert-heading))
  380. (buffer 'org-velocity-insert-heading))))
  381. (defun org-velocity-store-link ()
  382. "Function for `org-store-link-functions'."
  383. (if org-velocity-search
  384. (org-store-link-props
  385. :search org-velocity-search)))
  386. (add-hook 'org-store-link-functions 'org-velocity-store-link)
  387. (defun org-velocity-create (search &optional ask)
  388. "Create new heading named SEARCH.
  389. If ASK is non-nil, ask first."
  390. (when (or (null ask) (y-or-n-p "No match found, create? "))
  391. (let ((org-velocity-search search)
  392. (org-default-notes-file (org-velocity-use-file))
  393. ;; save a stored link
  394. org-store-link-plist)
  395. (org-velocity-create-1))
  396. search))
  397. (defun org-velocity-get-matches (search)
  398. "Return matches for SEARCH in current bucket.
  399. Use method specified by `org-velocity-search-method'."
  400. (when (and search (not (string-equal "" search)))
  401. (with-current-buffer (org-velocity-bucket-buffer)
  402. ;; Fold case if the search string is lowercase.
  403. (let ((case-fold-search (equal search (downcase search))))
  404. (case org-velocity-search-method
  405. ('phrase (org-velocity-phrase-search search))
  406. ('any (org-velocity-any-search search))
  407. ('all (org-velocity-all-search search))
  408. ('regexp (org-velocity-regexp-search search)))))))
  409. (defun org-velocity-engine (search)
  410. "Display a list of headings where SEARCH occurs."
  411. (with-current-buffer (org-velocity-display-buffer)
  412. (erase-buffer)
  413. (setq cursor-type nil))
  414. (unless (or
  415. (not (stringp search))
  416. (string-equal "" search)) ;exit on empty string
  417. (case
  418. (if (and org-velocity-force-new (eq last-command-event ?\C-j))
  419. 'force
  420. (with-current-buffer (org-velocity-bucket-buffer)
  421. (save-excursion
  422. (let ((matches (org-velocity-get-matches search)))
  423. (org-velocity-present matches nil search)
  424. (cond ((zerop (length matches)) 'new)
  425. ((= (length matches) 1) 'follow)
  426. ((> (length matches) 1) 'prompt))))))
  427. ('prompt (progn
  428. (Electric-pop-up-window (org-velocity-display-buffer))
  429. (let ((hint (org-velocity-electric-follow-hint)))
  430. (if hint
  431. (case hint
  432. (edit (org-velocity-read nil search))
  433. (force (org-velocity-create search))
  434. (otherwise (org-velocity-activate-button hint)))))))
  435. ('new (unless (org-velocity-create search t)
  436. (org-velocity-read nil search)))
  437. ('force (org-velocity-create search))
  438. ('follow (if (y-or-n-p "One match, follow? ")
  439. (progn
  440. (set-buffer (org-velocity-display-buffer))
  441. (goto-char (point-min))
  442. (button-activate (next-button (point))))
  443. (org-velocity-read nil search))))))
  444. (defun org-velocity-position (item list)
  445. "Return first position of ITEM in LIST."
  446. (loop for elt in list
  447. for i from 0
  448. if (equal elt item)
  449. return i))
  450. (defun org-velocity-activate-button (char)
  451. "Go to button on line number associated with CHAR in `org-velocity-index'."
  452. (goto-char (point-min))
  453. (forward-line (org-velocity-position char org-velocity-index))
  454. (goto-char
  455. (button-start
  456. (next-button (point))))
  457. (message "%s" (button-label (button-at (point))))
  458. (button-activate (button-at (point))))
  459. (defun org-velocity-electric-undefined ()
  460. "Complain about an undefined key."
  461. (interactive)
  462. (message "%s"
  463. (substitute-command-keys
  464. "\\[org-velocity-electric-new] for new entry, \\[org-velocity-electric-edit] to edit search, \\[scroll-up] to scroll."))
  465. (sit-for 4))
  466. (defun org-velocity-electric-follow (ev)
  467. "Follow a hint indexed by keyboard event EV."
  468. (interactive (list last-command-event))
  469. (if (not (> (org-velocity-position ev org-velocity-index)
  470. (1- (count-lines (point-min) (point-max)))))
  471. (throw 'org-velocity-select ev)
  472. (call-interactively 'org-velocity-electric-undefined)))
  473. (defun org-velocity-electric-click (ev)
  474. "Follow hint indexed by a mouse event EV."
  475. (interactive "e")
  476. (throw 'org-velocity-select
  477. (nth (1- (count-lines
  478. (point-min)
  479. (posn-point (event-start ev))))
  480. org-velocity-index)))
  481. (defun org-velocity-electric-edit ()
  482. "Edit the search string."
  483. (interactive)
  484. (throw 'org-velocity-select 'edit))
  485. (defun org-velocity-electric-new ()
  486. "Force a new entry."
  487. (interactive)
  488. (throw 'org-velocity-select 'force))
  489. (defvar org-velocity-electric-map
  490. (let ((map (make-sparse-keymap)))
  491. (define-key map [t] 'org-velocity-electric-undefined)
  492. (loop for c in org-velocity-index
  493. do (define-key map (char-to-string c) 'org-velocity-electric-follow))
  494. (define-key map "0" 'org-velocity-electric-new)
  495. (define-key map [tab] 'scroll-up)
  496. (define-key map [return] 'org-velocity-electric-edit)
  497. (define-key map [mouse-1] 'org-velocity-electric-click)
  498. (define-key map [mouse-2] 'org-velocity-electric-click)
  499. (define-key map [escape escape escape] 'keyboard-quit)
  500. (define-key map "\C-h" 'help-command)
  501. map))
  502. (defun org-velocity-electric-follow-hint ()
  503. "Read index of button electrically."
  504. (with-current-buffer (org-velocity-display-buffer)
  505. (use-local-map org-velocity-electric-map)
  506. (catch 'org-velocity-select
  507. (Electric-command-loop 'org-velocity-select
  508. "Follow: "))))
  509. (defvar org-velocity-incremental-keymap
  510. (let ((map (make-sparse-keymap)))
  511. (define-key map [mouse-1] 'org-velocity-click-for-incremental)
  512. (define-key map [mouse-2] 'org-velocity-click-for-incremental)
  513. map))
  514. (defun org-velocity-click-for-incremental ()
  515. "Jump out of search and select hint clicked on."
  516. (interactive)
  517. (let ((ev last-command-event))
  518. (org-velocity-activate-button
  519. (nth (- (count-lines
  520. (point-min)
  521. (posn-point (event-start ev))) 2)
  522. org-velocity-index)))
  523. (throw 'click (current-buffer)))
  524. (defun org-velocity-displaying-completions-p ()
  525. "Is there a *Completions* buffer showing?"
  526. (get-window-with-predicate
  527. (lambda (w)
  528. (eq (buffer-local-value 'major-mode (window-buffer w))
  529. 'completion-list-mode))))
  530. (defun org-velocity-display-for-incremental ()
  531. "Display results of search without hinting."
  532. (when (and (sit-for idle-update-delay)
  533. (not (org-velocity-displaying-completions-p)))
  534. (let* ((search (org-velocity-minibuffer-contents))
  535. (matches (org-velocity-get-matches search)))
  536. (if (zerop (length matches))
  537. (progn
  538. (when (get-buffer-window (org-velocity-display-buffer))
  539. (delete-window
  540. (get-buffer-window (org-velocity-display-buffer)))
  541. (select-window (active-minibuffer-window)))
  542. (unless (string-equal search "")
  543. (minibuffer-message "No match; RET to create")))
  544. (if (and org-velocity-exit-on-match
  545. (= (length matches) 1))
  546. (throw 'click search))
  547. (with-current-buffer (org-velocity-display-buffer)
  548. (use-local-map org-velocity-incremental-keymap)
  549. (erase-buffer)
  550. (setq cursor-type nil))
  551. (with-current-buffer (org-velocity-bucket-buffer)
  552. (org-velocity-present matches t search))
  553. (display-buffer (org-velocity-display-buffer))))))
  554. (defun org-velocity-dabbrev-completion-list (abbrev)
  555. "Return all dabbrev completions for ABBREV."
  556. ;; This is based on `dabbrev-completion'.
  557. (dabbrev--reset-global-variables)
  558. (setq dabbrev--last-abbrev abbrev)
  559. (dabbrev--find-all-expansions abbrev case-fold-search))
  560. (defun org-velocity-read-with-completion (prompt)
  561. "Completing read with PROMPT."
  562. (let ((minibuffer-local-completion-map
  563. minibuffer-local-filename-completion-map)
  564. (completion-no-auto-exit t)
  565. (crm-separator " "))
  566. (funcall
  567. (case org-velocity-search-method
  568. (phrase 'completing-read)
  569. (any 'completing-read-multiple)
  570. (all 'completing-read-multiple))
  571. prompt
  572. (completion-table-dynamic
  573. 'org-velocity-dabbrev-completion-list))))
  574. (defun org-velocity-read-string (prompt &optional initial-input)
  575. "Read string with PROMPT followed by INITIAL-INPUT."
  576. ;; The use of initial inputs to the minibuffer is deprecated (see
  577. ;; `read-from-minibuffer'), but in this case it is the user-friendly
  578. ;; thing to do.
  579. (minibuffer-with-setup-hook
  580. (lexical-let ((initial-input initial-input))
  581. (lambda ()
  582. (and initial-input (insert initial-input))
  583. (goto-char (point-max))))
  584. (if (eq org-velocity-search-method 'regexp)
  585. (read-regexp prompt)
  586. (if (and org-velocity-use-completion
  587. ;; map-entries complains for nonexistent files
  588. (file-exists-p (org-velocity-use-file)))
  589. (org-velocity-read-with-completion prompt)
  590. (read-string prompt)))))
  591. (defun org-velocity-read-incrementally (prompt)
  592. "Read string with PROMPT and display results incrementally."
  593. (let ((res
  594. (unwind-protect
  595. (catch 'click
  596. (add-hook 'post-command-hook
  597. 'org-velocity-display-for-incremental)
  598. (if (eq org-velocity-search-method 'regexp)
  599. (read-regexp prompt)
  600. (if (and org-velocity-use-completion
  601. (file-exists-p (org-velocity-use-file)))
  602. (org-velocity-read-with-completion prompt)
  603. (read-string prompt))))
  604. (remove-hook 'post-command-hook
  605. 'org-velocity-display-for-incremental))))
  606. (if (bufferp res) (switch-to-buffer res) res)))
  607. (defun org-velocity-read (arg &optional search)
  608. "Read a search string SEARCH for Org-Velocity interface.
  609. This means that a buffer will display all headings where SEARCH
  610. occurs, where one can be selected by a mouse click or by typing
  611. its index. If SEARCH does not occur, then a new heading may be
  612. created named SEARCH.
  613. If `org-velocity-bucket' is defined and
  614. `org-velocity-always-use-bucket' is non-nil, then the bucket file
  615. will be used; otherwise, this will work when called in any Org
  616. file. Calling with ARG forces current file."
  617. (interactive "P")
  618. (let ((org-velocity-always-use-bucket
  619. (if arg nil org-velocity-always-use-bucket)))
  620. ;; complain if inappropriate
  621. (assert (org-velocity-use-file))
  622. (unwind-protect
  623. (let ((dabbrev-search-these-buffers-only
  624. (list (org-velocity-bucket-buffer))))
  625. (org-velocity-engine
  626. (if org-velocity-search-is-incremental
  627. (org-velocity-read-incrementally "Velocity search: ")
  628. (org-velocity-read-string "Velocity search: " search))))
  629. (progn
  630. (kill-buffer (org-velocity-display-buffer))
  631. (delete-other-windows)))))
  632. (provide 'org-velocity)
  633. ;;; org-velocity.el ends here