Emacs config utilizing prelude as a base
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

550 lines
19 KiB

  1. ;;; helm-match-plugin.el --- Multiple regexp matching methods for helm
  2. ;; Original Author: rubikitch <rubikitch@ruby-lang.org>
  3. ;; This is a fork of `anything-match-plugin.el' created by
  4. ;; rubikitch <rubikitch@ruby-lang.org>
  5. ;; Maintainers: Thierry Volpiatto <thierry.volpiatto@gmail.com>
  6. ;; Le Wang
  7. ;; Copyright (C) 2008~2012, rubikitch, all rights reserved.
  8. ;; Copyright (C) 2011~2012, Thierry Volpiatto, all rights reserved.
  9. ;; Keywords: helm, matching
  10. ;; X-URL: <https://github.com/emacs-helm/helm>
  11. ;; Created: 2012-03-15 12:29:23
  12. ;; This file is free software; you can redistribute it and/or modify
  13. ;; it under the terms of the GNU General Public License as published by
  14. ;; the Free Software Foundation; either version 2, or (at your option)
  15. ;; any later version.
  16. ;; This file is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. ;; GNU General Public License for more details.
  20. ;; You should have received a copy of the GNU General Public License
  21. ;; along with GNU Emacs; see the file COPYING. If not, write to
  22. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  23. ;; Boston, MA 02110-1301, USA.
  24. ;;; Auto documentation
  25. ;; ------------------
  26. ;;
  27. ;; * User variables
  28. ;; [EVAL] (autodoc-document-lisp-buffer :type 'user-variable :prefix "helm-mp" :var-value t)
  29. ;; `helm-mp-matching-method'
  30. ;; Default Value: multi3
  31. ;; `helm-mp-highlight-delay'
  32. ;; Default Value: 0.7
  33. ;; `helm-mp-highlight-threshold'
  34. ;; Default Value: 2
  35. ;;
  36. ;; * Internal variables
  37. ;; [EVAL] (autodoc-document-lisp-buffer :type 'internal-variable :prefix "helm-mp" :var-value t)
  38. ;; `helm-mp-default-match-functions'
  39. ;; Default Value: (helm-mp-exact-match helm-mp-3-match)
  40. ;; `helm-mp-default-search-functions'
  41. ;; Default Value: (helm-mp-exact-search helm-mp-3-search)
  42. ;; `helm-mp-default-search-backward-functions'
  43. ;; Default Value: (helm-mp-exact-search-backward helm-mp-3-search-backward)
  44. ;; `helm-mp-space-regexp'
  45. ;; Default Value: "[\\ ] "
  46. ;; `helm-mp-exact-pattern-str'
  47. ;; Default Value: "autod"
  48. ;; `helm-mp-exact-pattern-real'
  49. ;; Default Value: "\nautod\n"
  50. ;; `helm-mp-prefix-pattern-str'
  51. ;; Default Value: nil
  52. ;; `helm-mp-prefix-pattern-real'
  53. ;; Default Value: nil
  54. ;; `helm-mp-1-pattern-str'
  55. ;; Default Value: nil
  56. ;; `helm-mp-1-pattern-real'
  57. ;; Default Value: nil
  58. ;; `helm-mp-2-pattern-str'
  59. ;; Default Value: nil
  60. ;; `helm-mp-2-pattern-real'
  61. ;; Default Value: nil
  62. ;; `helm-mp-3-pattern-str'
  63. ;; Default Value: "autod"
  64. ;; `helm-mp-3-pattern-list'
  65. ;; Default Value: ((identity . "autod"))
  66. ;; `helm-mp-initial-highlight-delay'
  67. ;; Default Value: nil
  68. ;;
  69. ;; * Helm match plugin Functions
  70. ;; [EVAL] (autodoc-document-lisp-buffer :type 'function :prefix "helm-mp")
  71. ;; `helm-mp-set-matching-method'
  72. ;; `helm-mp-make-regexps'
  73. ;; `helm-mp-1-make-regexp'
  74. ;; `helm-mp-exact-get-pattern'
  75. ;; `helm-mp-exact-match'
  76. ;; `helm-mp-exact-search'
  77. ;; `helm-mp-exact-search-backward'
  78. ;; `helm-mp-prefix-get-pattern'
  79. ;; `helm-mp-prefix-match'
  80. ;; `helm-mp-prefix-search'
  81. ;; `helm-mp-prefix-search-backward'
  82. ;; `helm-mp-1-get-pattern'
  83. ;; `helm-mp-1-match'
  84. ;; `helm-mp-1-search'
  85. ;; `helm-mp-1-search-backward'
  86. ;; `helm-mp-2-get-pattern'
  87. ;; `helm-mp-2-match'
  88. ;; `helm-mp-2-search'
  89. ;; `helm-mp-2-search-backward'
  90. ;; `helm-mp-3-get-patterns'
  91. ;; `helm-mp-3-get-patterns-internal'
  92. ;; `helm-mp-3-match'
  93. ;; `helm-mp-3-search-base'
  94. ;; `helm-mp-3-search'
  95. ;; `helm-mp-3-search-backward'
  96. ;; `helm-mp-3p-match'
  97. ;; `helm-mp-3p-search'
  98. ;; `helm-mp-3p-search-backward'
  99. ;; `helm-mp-highlight-match'
  100. ;; `helm-mp-highlight-region'
  101. ;; `helm-mp-highlight-match-internal'
  102. ;; *** END auto-documentation
  103. ;;; Commentary:
  104. ;; Change helm.el matching algorithm humanely.
  105. ;; It gives helm.el search refinement functionality.
  106. ;; exact match -> prefix match -> multiple regexp match
  107. ;; A query of multiple regexp match is space-delimited string.
  108. ;; Helm displays candidates which matches all the regexps.
  109. ;; A regexp with "!" prefix means not matching the regexp.
  110. ;; To include spaces to a regexp, prefix "\" before space,
  111. ;; it is controlled by `helm-mp-space-regexp' variable.
  112. ;; This file highlights patterns like `occur'. Note that patterns
  113. ;; longer than `helm-mp-highlight-threshold' are highlighted. And
  114. ;; region out of screen is highlighted after
  115. ;; `helm-mp-highlight-delay' seconds.
  116. ;; Highlight in Emacs is time-consuming process for slow computers. To
  117. ;; disable it is to set nil to `helm-mp-highlight-delay'.
  118. ;; helm-match-plugin is enable by default in helm.
  119. ;; To disable/enable it use M-x helm-c-toggle-match-plugin.
  120. ;;; Code:
  121. (require 'helm)
  122. (require 'cl)
  123. ;;;; Match-plugin
  124. ;; Internal
  125. (defvar helm-mp-default-match-functions nil)
  126. (defvar helm-mp-default-search-functions nil)
  127. (defvar helm-mp-default-search-backward-functions nil)
  128. (defun helm-mp-set-matching-method (var key)
  129. "Default function to set matching methods in helm match plugin."
  130. (set-default var key)
  131. (case (symbol-value var)
  132. (multi1 (setq helm-mp-default-match-functions
  133. '(helm-mp-exact-match helm-mp-1-match)
  134. helm-mp-default-search-functions
  135. '(helm-mp-exact-search helm-mp-1-search)
  136. helm-mp-default-search-backward-functions
  137. '(helm-mp-exact-search-backward
  138. helm-mp-1-search-backward)))
  139. (multi2 (setq helm-mp-default-match-functions
  140. '(helm-mp-exact-match helm-mp-2-match)
  141. helm-mp-default-search-functions
  142. '(helm-mp-exact-search helm-mp-2-search)
  143. helm-mp-default-search-backward-functions
  144. '(helm-mp-exact-search-backward
  145. helm-mp-2-search-backward)))
  146. (multi3 (setq helm-mp-default-match-functions
  147. '(helm-mp-exact-match helm-mp-3-match)
  148. helm-mp-default-search-functions
  149. '(helm-mp-exact-search helm-mp-3-search)
  150. helm-mp-default-search-backward-functions
  151. '(helm-mp-exact-search-backward
  152. helm-mp-3-search-backward)))
  153. (multi3p (setq helm-mp-default-match-functions
  154. '(helm-mp-exact-match helm-mp-3p-match)
  155. helm-mp-default-search-functions
  156. '(helm-mp-exact-search helm-mp-3p-search)
  157. helm-mp-default-search-backward-functions
  158. '(helm-mp-exact-search-backward
  159. helm-mp-3p-search-backward)))
  160. (t (error "Unknow value: %s" helm-mp-matching-method))))
  161. (defgroup helm-match-plugin nil
  162. "Helm match plugin."
  163. :group 'helm)
  164. (defcustom helm-mp-matching-method 'multi3
  165. "Matching method for helm match plugin.
  166. You can set here different methods to match candidates in helm.
  167. Here are the possible value of this symbol and their meaning:
  168. - multi1: Respect order, prefix of pattern must match.
  169. - multi2: Same but with partial match.
  170. - multi3: The best, multiple regexp match, allow negation.
  171. - multi3p: Same but prefix must match.
  172. Default is multi3."
  173. :type '(radio :tag "Matching methods for helm"
  174. (const :tag "Multiple regexp 1 ordered with prefix match" multi1)
  175. (const :tag "Multiple regexp 2 ordered with partial match" multi2)
  176. (const :tag "Multiple regexp 3 matching no order, partial, best." multi3)
  177. (const :tag "Multiple regexp 3p matching with prefix match" multi3p))
  178. :set 'helm-mp-set-matching-method
  179. :group 'helm-match-plugin)
  180. (defface helm-match
  181. '((t (:inherit match)))
  182. "Face used to highlight matches."
  183. :group 'helm-match-plugin)
  184. (defcustom helm-mp-highlight-delay 0.7
  185. "Highlight matches with `helm-match' face after this many seconds.
  186. If nil, no highlight. "
  187. :type 'integer
  188. :group 'helm-match-plugin)
  189. (defcustom helm-mp-highlight-threshold 2
  190. "Minimum length of pattern to highlight.
  191. The smaller this value is, the slower highlight is."
  192. :type 'integer
  193. :group 'helm-match-plugin)
  194. ;;; Build regexps
  195. ;;
  196. ;;
  197. (defvar helm-mp-space-regexp "[\\ ] "
  198. "Regexp to represent space itself in multiple regexp match.")
  199. (defun helm-mp-make-regexps (pattern)
  200. "Split PATTERN if it contain spaces and return resulting list.
  201. If spaces in PATTERN are escaped, don't split at this place.
  202. i.e \"foo bar\"=> (\"foo\" \"bar\")
  203. but \"foo\ bar\"=> (\"foobar\")."
  204. (if (string= pattern "")
  205. '("")
  206. (loop for s in (split-string
  207. (replace-regexp-in-string helm-mp-space-regexp
  208. "\000\000" pattern)
  209. " " t)
  210. collect (replace-regexp-in-string "\000\000" " " s))))
  211. (defun helm-mp-1-make-regexp (pattern)
  212. "Replace spaces in PATTERN with \"\.*\"."
  213. (mapconcat 'identity (helm-mp-make-regexps pattern) ".*"))
  214. ;;; Exact match.
  215. ;;
  216. ;;
  217. ;; Internal.
  218. (defvar helm-mp-exact-pattern-str nil)
  219. (defvar helm-mp-exact-pattern-real nil)
  220. (defun helm-mp-exact-get-pattern (pattern)
  221. (unless (equal pattern helm-mp-exact-pattern-str)
  222. (setq helm-mp-exact-pattern-str pattern
  223. helm-mp-exact-pattern-real (concat "\n" pattern "\n")))
  224. helm-mp-exact-pattern-real)
  225. (defun helm-mp-exact-match (str &optional pattern)
  226. (string= str (or pattern helm-pattern)))
  227. (defun helm-mp-exact-search (pattern &rest ignore)
  228. (and (search-forward (helm-mp-exact-get-pattern pattern) nil t)
  229. (forward-line -1)))
  230. (defun helm-mp-exact-search-backward (pattern &rest ignore)
  231. (and (search-backward (helm-mp-exact-get-pattern pattern) nil t)
  232. (forward-line 1)))
  233. ;;; Prefix match
  234. ;;
  235. ;;
  236. ;; Internal
  237. (defvar helm-mp-prefix-pattern-str nil)
  238. (defvar helm-mp-prefix-pattern-real nil)
  239. (defun helm-mp-prefix-get-pattern (pattern)
  240. (unless (equal pattern helm-mp-prefix-pattern-str)
  241. (setq helm-mp-prefix-pattern-str pattern
  242. helm-mp-prefix-pattern-real (concat "\n" pattern)))
  243. helm-mp-prefix-pattern-real)
  244. (defun helm-mp-prefix-match (str &optional pattern)
  245. (setq pattern (or pattern helm-pattern))
  246. (let ((len (length pattern)))
  247. (and (<= len (length str))
  248. (string= (substring str 0 len) pattern ))))
  249. (defun helm-mp-prefix-search (pattern &rest ignore)
  250. (search-forward (helm-mp-prefix-get-pattern pattern) nil t))
  251. (defun helm-mp-prefix-search-backward (pattern &rest ignore)
  252. (and (search-backward (helm-mp-prefix-get-pattern pattern) nil t)
  253. (forward-line 1)))
  254. ;;; Multiple regexp patterns 1 (order is preserved / prefix).
  255. ;;
  256. ;;
  257. ;; Internal
  258. (defvar helm-mp-1-pattern-str nil)
  259. (defvar helm-mp-1-pattern-real nil)
  260. (defun helm-mp-1-get-pattern (pattern)
  261. (unless (equal pattern helm-mp-1-pattern-str)
  262. (setq helm-mp-1-pattern-str pattern
  263. helm-mp-1-pattern-real
  264. (concat "^" (helm-mp-1-make-regexp pattern))))
  265. helm-mp-1-pattern-real)
  266. (defun* helm-mp-1-match (str &optional (pattern helm-pattern))
  267. (string-match (helm-mp-1-get-pattern pattern) str))
  268. (defun helm-mp-1-search (pattern &rest ignore)
  269. (re-search-forward (helm-mp-1-get-pattern pattern) nil t))
  270. (defun helm-mp-1-search-backward (pattern &rest ignore)
  271. (re-search-backward (helm-mp-1-get-pattern pattern) nil t))
  272. ;;; Multiple regexp patterns 2 (order is preserved / partial).
  273. ;;
  274. ;;
  275. ;; Internal
  276. (defvar helm-mp-2-pattern-str nil)
  277. (defvar helm-mp-2-pattern-real nil)
  278. (defun helm-mp-2-get-pattern (pattern)
  279. (unless (equal pattern helm-mp-2-pattern-str)
  280. (setq helm-mp-2-pattern-str pattern
  281. helm-mp-2-pattern-real
  282. (concat "^.*" (helm-mp-1-make-regexp pattern))))
  283. helm-mp-2-pattern-real)
  284. (defun* helm-mp-2-match (str &optional (pattern helm-pattern))
  285. (string-match (helm-mp-2-get-pattern pattern) str))
  286. (defun helm-mp-2-search (pattern &rest ignore)
  287. (re-search-forward (helm-mp-2-get-pattern pattern) nil t))
  288. (defun helm-mp-2-search-backward (pattern &rest ignore)
  289. (re-search-backward (helm-mp-2-get-pattern pattern) nil t))
  290. ;;; Multiple regexp patterns 3 (permutation).
  291. ;;
  292. ;;
  293. ;; Internal
  294. (defvar helm-mp-3-pattern-str nil)
  295. (defvar helm-mp-3-pattern-list nil)
  296. (defun helm-mp-3-get-patterns (pattern)
  297. "Return `helm-mp-3-pattern-list', a list of predicate/regexp cons cells.
  298. e.g ((identity . \"foo\") (identity . \"bar\")).
  299. This is done only if `helm-mp-3-pattern-str' is same as PATTERN."
  300. (unless (equal pattern helm-mp-3-pattern-str)
  301. (setq helm-mp-3-pattern-str pattern
  302. helm-mp-3-pattern-list
  303. (helm-mp-3-get-patterns-internal pattern)))
  304. helm-mp-3-pattern-list)
  305. (defun helm-mp-3-get-patterns-internal (pattern)
  306. "Return a list of predicate/regexp cons cells.
  307. e.g ((identity . \"foo\") (identity . \"bar\"))."
  308. (unless (string= pattern "")
  309. (loop for pat in (helm-mp-make-regexps pattern)
  310. collect (if (string= "!" (substring pat 0 1))
  311. (cons 'not (substring pat 1))
  312. (cons 'identity pat)))))
  313. (defun helm-mp-3-match (str &optional pattern)
  314. "Check if PATTERN match STR.
  315. When PATTERN contain a space, it is splitted and matching is done
  316. with the several resulting regexps against STR.
  317. e.g \"bar foo\" will match \"foobar\" and \"barfoo\".
  318. Argument PATTERN, a string, is transformed in a list of
  319. cons cell with `helm-mp-3-get-patterns' if it contain a space.
  320. e.g \"foo bar\"=>((identity . \"foo\") (identity . \"bar\")).
  321. Then each predicate of cons cell(s) is called with regexp of same
  322. cons cell against STR (a candidate).
  323. i.e (identity (string-match \"foo\" \"foo bar\")) => t."
  324. (let ((pat (helm-mp-3-get-patterns (or pattern helm-pattern))))
  325. (loop for (predicate . regexp) in pat
  326. always (funcall predicate (string-match regexp str)))))
  327. (defun helm-mp-3-search-base (pattern searchfn1 searchfn2)
  328. (loop with pat = (if (stringp pattern)
  329. (helm-mp-3-get-patterns pattern)
  330. pattern)
  331. while (funcall searchfn1 (or (cdar pat) "") nil t)
  332. for bol = (point-at-bol)
  333. for eol = (point-at-eol)
  334. if (loop for (pred . str) in (cdr pat) always
  335. (progn (goto-char bol)
  336. (funcall pred (funcall searchfn2 str eol t))))
  337. do (goto-char eol) and return t
  338. else do (goto-char eol)
  339. finally return nil))
  340. (defun helm-mp-3-search (pattern &rest ignore)
  341. (when (stringp pattern)
  342. (setq pattern (helm-mp-3-get-patterns pattern)))
  343. (helm-mp-3-search-base
  344. pattern 're-search-forward 're-search-forward))
  345. (defun helm-mp-3-search-backward (pattern &rest ignore)
  346. (when (stringp pattern)
  347. (setq pattern (helm-mp-3-get-patterns pattern)))
  348. (helm-mp-3-search-base
  349. pattern 're-search-backward 're-search-backward))
  350. ;;; mp-3p- (multiple regexp pattern 3 with prefix search)
  351. ;;
  352. ;;
  353. (defun helm-mp-3p-match (str &optional pattern)
  354. "Check if PATTERN match STR.
  355. Same as `helm-mp-3-match' but more strict, matching against prefix also.
  356. e.g \"bar foo\" will match \"barfoo\" but not \"foobar\" contrarily to
  357. `helm-mp-3-match'."
  358. (let* ((pat (helm-mp-3-get-patterns (or pattern helm-pattern)))
  359. (first (car pat)))
  360. (and (funcall (car first) (helm-mp-prefix-match str (cdr first)))
  361. (loop for (predicate . regexp) in (cdr pat)
  362. always (funcall predicate (string-match regexp str))))))
  363. (defun helm-mp-3p-search (pattern &rest ignore)
  364. (when (stringp pattern)
  365. (setq pattern (helm-mp-3-get-patterns pattern)))
  366. (helm-mp-3-search-base
  367. pattern 'helm-mp-prefix-search 're-search-forward))
  368. (defun helm-mp-3p-search-backward (pattern &rest ignore)
  369. (when (stringp pattern)
  370. (setq pattern (helm-mp-3-get-patterns pattern)))
  371. (helm-mp-3-search-base
  372. pattern 'helm-mp-prefix-search-backward 're-search-backward))
  373. ;;; source compiler
  374. ;;
  375. ;;
  376. (defun helm-compile-source--match-plugin (source)
  377. (let ((searchers (if (assoc 'search-from-end source)
  378. helm-mp-default-search-backward-functions
  379. helm-mp-default-search-functions)))
  380. `(,(if (or (assoc 'candidates-in-buffer source)
  381. (equal '(identity) (assoc-default 'match source)))
  382. '(match identity)
  383. `(match ,@helm-mp-default-match-functions
  384. ,@(assoc-default 'match source)))
  385. (search ,@searchers
  386. ,@(assoc-default 'search source))
  387. ,@source)))
  388. (add-to-list 'helm-compile-source-functions 'helm-compile-source--match-plugin t)
  389. ;;; Highlight matches.
  390. ;;
  391. ;;
  392. (defun helm-mp-highlight-match ()
  393. "Highlight matches after `helm-mp-highlight-delay' seconds."
  394. (when (and helm-mp-highlight-delay
  395. (not (string= helm-pattern "")))
  396. (helm-mp-highlight-match-internal (window-end (helm-window)))
  397. (run-with-idle-timer helm-mp-highlight-delay nil
  398. 'helm-mp-highlight-match-internal
  399. (with-current-buffer helm-buffer (point-max)))))
  400. (add-hook 'helm-update-hook 'helm-mp-highlight-match)
  401. (defun helm-mp-highlight-region (start end regexp face)
  402. (save-excursion
  403. (goto-char start)
  404. (let (me)
  405. (while (and (setq me (re-search-forward regexp nil t))
  406. (< (point) end)
  407. (< 0 (- (match-end 0) (match-beginning 0))))
  408. (unless (helm-pos-header-line-p)
  409. (put-text-property (match-beginning 0) me 'face face))))))
  410. (defun helm-mp-highlight-match-internal (end)
  411. (when (helm-window)
  412. (set-buffer helm-buffer)
  413. (let ((requote (loop for (pred . re) in
  414. (helm-mp-3-get-patterns helm-pattern)
  415. when (and (eq pred 'identity)
  416. (>= (length re)
  417. helm-mp-highlight-threshold))
  418. collect re into re-list
  419. finally return
  420. (if (and re-list (>= (length re-list) 1))
  421. (mapconcat 'identity re-list "\\|")
  422. (regexp-quote helm-pattern)))))
  423. (when (>= (length requote) helm-mp-highlight-threshold)
  424. (helm-mp-highlight-region
  425. (point-min) end requote 'helm-match)))))
  426. ;;; Toggle helm-match-plugin
  427. ;;
  428. ;;
  429. (defvar helm-mp-initial-highlight-delay nil)
  430. ;;;###autoload
  431. (defun helm-mp-toggle-match-plugin ()
  432. "Turn on/off multiple regexp matching in helm.
  433. i.e helm-match-plugin."
  434. (interactive)
  435. (let ((helm-match-plugin-enabled
  436. (member 'helm-compile-source--match-plugin
  437. helm-compile-source-functions)))
  438. (flet ((disable-match-plugin ()
  439. (setq helm-compile-source-functions
  440. (delq 'helm-compile-source--match-plugin
  441. helm-compile-source-functions))
  442. (setq helm-mp-initial-highlight-delay
  443. helm-mp-highlight-delay)
  444. (setq helm-mp-highlight-delay nil))
  445. (enable-match-plugin ()
  446. (unless helm-mp-initial-highlight-delay
  447. (setq helm-mp-initial-highlight-delay
  448. helm-mp-highlight-delay))
  449. (setq helm-compile-source-functions
  450. (cons 'helm-compile-source--match-plugin
  451. helm-compile-source-functions))
  452. (unless helm-mp-highlight-delay
  453. (setq helm-mp-highlight-delay
  454. helm-mp-initial-highlight-delay))))
  455. (if helm-match-plugin-enabled
  456. (when (y-or-n-p "Really disable match-plugin? ")
  457. (disable-match-plugin)
  458. (message "Helm-match-plugin disabled"))
  459. (when (y-or-n-p "Really enable match-plugin? ")
  460. (enable-match-plugin)
  461. (message "Helm-match-plugin enabled"))))))
  462. ;;;; Unit test
  463. ;;
  464. ;; unit test for match plugin are now in developper-tools/unit-test-match-plugin.el
  465. (provide 'helm-match-plugin)
  466. ;;; helm-match-plugin.el ends here