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.

425 lines
16 KiB

  1. ;;; ack-and-a-half.el --- Yet another front-end for ack
  2. ;;
  3. ;; Copyright (C) 2011 Jacob Helwig
  4. ;;
  5. ;; Author: Jacob Helwig <jacob+ack * technosorcery.net>
  6. ;; Version: 0.0.1
  7. ;; Homepage: http://technosorcery.net
  8. ;;
  9. ;; This file is NOT part of GNU Emacs.
  10. ;;
  11. ;; This program is free software; you can redistribute it and/or
  12. ;; modify it under the terms of the GNU General Public License as
  13. ;; published by the Free Software Foundation; either version 2, or (at
  14. ;; your option) any later version.
  15. ;;
  16. ;; This program is distributed in the hope that it will be useful, but
  17. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. ;; General Public License for more details.
  20. ;;
  21. ;; You should have received a copy of the GNU General Public License
  22. ;; along with this program ; see the file COPYING. If not, write to
  23. ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  24. ;; Boston, MA 02111-1307, USA.
  25. ;;
  26. ;;; Commentary:
  27. ;;
  28. ;; ack-and-a-half.el provides a simple compilation mode for the perl
  29. ;; grep-a-like ack (http://petdance.com/ack/).
  30. ;;
  31. ;; Add the following to your .emacs:
  32. ;;
  33. ;; (add-to-list 'load-path "/path/to/ack-and-a-half")
  34. ;; (autoload 'ack-and-a-half-same "ack-and-a-half" nil t)
  35. ;; (autoload 'ack-and-a-half "ack-and-a-half" nil t)
  36. ;; (autoload 'ack-and-a-half-find-file-same "ack-and-a-half" nil t)
  37. ;; (autoload 'ack-and-a-half-find-file "ack-and-a-half" nil t)
  38. ;; (defalias 'ack 'ack-and-a-half)
  39. ;; (defalias 'ack-same 'ack-and-a-half-same)
  40. ;; (defalias 'ack-find-file 'ack-and-a-half-find-file)
  41. ;; (defalias 'ack-find-file-same 'ack-and-a-half-find-file-same)
  42. ;;
  43. ;; Run `ack' to search for all files and `ack-same' to search for
  44. ;; files of the same type as the current buffer.
  45. ;;
  46. ;; `next-error' and `previous-error' can be used to jump to the
  47. ;; matches.
  48. ;;
  49. ;; `ack-find-file' and `ack-find-same-file' use ack to list the files
  50. ;; in the current project. It's a convenient, though slow, way of
  51. ;; finding files.
  52. ;;
  53. (eval-when-compile (require 'cl))
  54. (require 'compile)
  55. (require 'grep)
  56. (add-to-list 'debug-ignored-errors
  57. "^Moved \\(back before fir\\|past la\\)st match$")
  58. (add-to-list 'debug-ignored-errors "^File .* not found$")
  59. (define-compilation-mode ack-and-a-half-mode "Ack"
  60. "Ack results compilation mode."
  61. (set (make-local-variable 'compilation-disable-input) t)
  62. (set (make-local-variable 'compilation-error-face) grep-hit-face))
  63. (defgroup ack-and-a-half nil "Yet another front end for ack."
  64. :group 'tools
  65. :group 'matching)
  66. ; TODO Determine how to fall back to using ack-grep if ack is not found.
  67. (defcustom ack-and-a-half-executable (executable-find "ack")
  68. "*The location of the ack executable"
  69. :group 'ack-and-a-half
  70. :type 'file)
  71. (defcustom ack-and-a-half-arguments nil
  72. "*Extra arguments to pass to ack."
  73. :group 'ack-and-a-half
  74. :type '(repeat (string)))
  75. (defcustom ack-and-a-half-mode-type-alist nil
  76. "*File type(s) to search per major mode. (ack-and-a-half-same)
  77. This overrides values in `ack-and-a-half-mode-type-default-alist'.
  78. The car in each list element is a major mode, and the rest
  79. is a list of strings passed to the --type flag of ack when running
  80. `ack-and-a-half-same'."
  81. :group 'ack-and-a-half
  82. :type '(repeat (cons (symbol :tag "Major mode")
  83. (repeat (string :tag "ack --type")))))
  84. (defcustom ack-and-a-half-mode-extension-alist nil
  85. "*File extensions to search per major mode. (ack-and-a-half-same)
  86. This overrides values in `ack-and-a-half-mode-extension-default-alist'.
  87. The car in each list element is a major mode, and the rest
  88. is a list of file extensions to be searched in addition to
  89. the type defined in `ack-and-a-half-mode-type-alist' when
  90. running `ack-and-a-half-same'."
  91. :group 'ack-and-a-half
  92. :type '(repeat (cons (symbol :tag "Major mode")
  93. (repeat :tag "File extensions" (string)))))
  94. (defcustom ack-and-a-half-ignore-case 'smart
  95. "*Ignore case when searching
  96. The special value 'smart enables the ack option \"smart-case\"."
  97. :group 'ack-and-a-half
  98. :type '(choice (const :tag "Case sensitive" nil)
  99. (const :tag "Smart case" 'smart)
  100. (const :tag "Case insensitive" t)))
  101. (defcustom ack-and-a-half-regexp-search t
  102. "*Default to regular expression searching.
  103. Giving a prefix argument to `ack-and-a-half' toggles this option."
  104. :group 'ack-and-a-half
  105. :type '(choice (const :tag "Literal searching" nil)
  106. (const :tag "Regular expression searching" t)))
  107. (defcustom ack-and-a-half-use-environment t
  108. "*Use .ackrc and ACK_OPTIONS when searching."
  109. :group 'ack-and-a-half
  110. :type '(choice (const :tag "Ignore environment" nil)
  111. (const :tag "Use environment" t)))
  112. (defcustom ack-and-a-half-root-directory-functions '(ack-and-a-half-guess-project-root)
  113. "*List of functions used to find the base directory to ack from.
  114. These functions are called until one returns a directory. If successful,
  115. `ack-and-a-half' is run from that directory instead of from `default-directory'.
  116. The directory is verified by the user depending on `ack-and-a-half-prompt-for-directory'."
  117. :group 'ack-and-a-half
  118. :type '(repeat function))
  119. (defcustom ack-and-a-half-project-root-file-patterns
  120. '(".project\\'"
  121. ".xcodeproj\\'"
  122. ".sln\\'"
  123. "\\`Project.ede\\'"
  124. "\\`.git\\'"
  125. "\\`.bzr\\'"
  126. "\\`_darcs\\'"
  127. "\\`.hg\\'")
  128. "*List of file patterns for the project root (used by `ack-and-a-half-guess-project-root'.
  129. Each element is a regular expression. If a file matching any element is
  130. found in a directory, then that directory is assumed to be the project
  131. root by `ack-and-a-half-guess-project-root'."
  132. :group 'ack-and-a-half
  133. :type '(repeat (string :tag "Regular expression")))
  134. (defcustom ack-and-a-half-prompt-for-directory 'unless-guessed
  135. "*Prompt for directory in which to run ack.
  136. If this is 'unless-guessed, then the value determined by `ack-and-a-half-root-directory-functions'
  137. is used without confirmation. If it is nil, then the directory is never
  138. confirmed. If t, then always prompt for the directory to use."
  139. :group 'ack-and-a-half
  140. :type '(choice (const :tag "Don't prompt" nil)
  141. (const :tag "Don't prompt when guessed" 'unless-guessed)
  142. (const :tag "Always prompt" t)))
  143. ;;; Default setting lists ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  144. (defconst ack-and-a-half-mode-type-default-alist
  145. '((actionscript-mode "actionscript")
  146. (LaTeX-mode "tex")
  147. (TeX-mode "tex")
  148. (asm-mode "asm")
  149. (batch-file-mode "batch")
  150. (c++-mode "cpp")
  151. (c-mode "cc")
  152. (cfmx-mode "cfmx")
  153. (cperl-mode "perl")
  154. (csharp-mode "csharp")
  155. (css-mode "css")
  156. (emacs-lisp-mode "elisp")
  157. (erlang-mode "erlang")
  158. (espresso-mode "java")
  159. (fortran-mode "fortran")
  160. (haskell-mode "haskell")
  161. (hexl-mode "binary")
  162. (html-mode "html")
  163. (java-mode "java")
  164. (javascript-mode "js")
  165. (jde-mode "java")
  166. (js2-mode "js")
  167. (jsp-mode "jsp")
  168. (latex-mode "tex")
  169. (lisp-mode "lisp")
  170. (lua-mode "lua")
  171. (makefile-mode "make")
  172. (mason-mode "mason")
  173. (nxml-mode "xml")
  174. (objc-mode "objc" "objcpp")
  175. (ocaml-mode "ocaml")
  176. (parrot-mode "parrot")
  177. (perl-mode "perl")
  178. (php-mode "php")
  179. (plone-mode "plone")
  180. (python-mode "python")
  181. (ruby-mode "ruby")
  182. (scheme-mode "scheme")
  183. (shell-script-mode "shell")
  184. (skipped-mode "skipped")
  185. (smalltalk-mode "smalltalk")
  186. (sql-mode "sql")
  187. (tcl-mode "tcl")
  188. (tex-mode "tex")
  189. (tt-mode "tt")
  190. (vb-mode "vb")
  191. (vim-mode "vim")
  192. (xml-mode "xml")
  193. (yaml-mode "yaml"))
  194. "Default values for `ack-and-a-half-mode-type-alist'.")
  195. (defconst ack-and-a-half-mode-extension-default-alist
  196. '((d-mode "d"))
  197. "Default values for `ack-and-a-half-mode-extension-alist'.")
  198. (defun ack-and-a-half-create-type (extensions)
  199. (list "--type-set"
  200. (concat "ack-and-a-half-custom-type=" (mapconcat 'identity extensions ","))
  201. "--type" "ack-and-a-half-custom-type"))
  202. (defun ack-and-a-half-type-for-major-mode (mode)
  203. "Return the --type and --type-set arguments to use with ack for major mode MODE."
  204. (let ((types (cdr (or (assoc mode ack-and-a-half-mode-type-alist)
  205. (assoc mode ack-and-a-half-mode-type-default-alist))))
  206. (ext (cdr (or (assoc mode ack-and-a-half-mode-extension-alist)
  207. (assoc mode ack-and-a-half-mode-extension-default-alist))))
  208. result)
  209. (dolist (type types)
  210. (push type result)
  211. (push "--type" result))
  212. (if ext
  213. (if types
  214. `("--type-add" ,(concat (car types)
  215. "=" (mapconcat 'identity ext ","))
  216. . ,result)
  217. (ack-and-a-half-create-type ext))
  218. result)))
  219. ;;; Project root ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  220. (defun ack-and-a-half-guess-project-root ()
  221. "Guess the project root directory.
  222. This is intended to be used in `ack-and-a-half-root-directory-functions'."
  223. (catch 'root
  224. (let ((dir (expand-file-name (if buffer-file-name
  225. (file-name-directory buffer-file-name)
  226. default-directory)))
  227. (pattern (mapconcat 'identity ack-and-a-half-project-root-file-patterns "\\|")))
  228. (while (not (equal dir "/"))
  229. (when (directory-files dir nil pattern t)
  230. (throw 'root dir))
  231. (setq dir (file-name-directory (directory-file-name dir)))))))
  232. ;;; Commands ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  233. (defvar ack-and-a-half-directory-history nil
  234. "Directories recently searched with `ack-and-a-half'.")
  235. (defvar ack-and-a-half-literal-history nil
  236. "Strings recently searched for with `ack-and-a-half'.")
  237. (defvar ack-and-a-half-regexp-history nil
  238. "Regular expressions recently searched for with `ack-and-a-half'.")
  239. (defsubst ack-and-a-half-read (regexp)
  240. (read-from-minibuffer (if regexp "ack pattern: " "ack literal search: ")
  241. nil nil nil
  242. (if regexp 'ack-and-a-half-regexp-history 'ack-and-a-half-literal-history)))
  243. (defun ack-and-a-half-read-dir ()
  244. (let ((dir (run-hook-with-args-until-success 'ack-and-a-half-root-directory-functions)))
  245. (if ack-and-a-half-prompt-for-directory
  246. (if (and dir (eq ack-and-a-half-prompt-for-directory 'unless-guessed))
  247. dir
  248. (read-directory-name "Directory: " dir dir t))
  249. (or dir
  250. (and buffer-file-name (file-name-and-directory buffer-file-name))
  251. default-directory))))
  252. (defsubst ack-and-a-half-xor (a b)
  253. (if a (not b) b))
  254. (defun ack-and-a-half-interactive ()
  255. "Return the (interactive) arguments for `ack-and-a-half' and `ack-and-a-half-same'."
  256. (let ((regexp (ack-and-a-half-xor current-prefix-arg ack-and-a-half-regexp-search)))
  257. (list (ack-and-a-half-read regexp)
  258. regexp
  259. (ack-and-a-half-read-dir))))
  260. (defun ack-and-a-half-type ()
  261. (or (ack-and-a-half-type-for-major-mode major-mode)
  262. (when buffer-file-name
  263. (ack-and-a-half-create-type (list (file-name-extension buffer-file-name))))))
  264. (defun ack-and-a-half-option (name enabled)
  265. (format "--%s%s" (if enabled "" "no") name))
  266. (defun ack-and-a-half-arguments-from-options (regexp)
  267. (let ((arguments (list "--nocolor" "--nogroup"
  268. (ack-and-a-half-option "smart-case" (eq ack-and-a-half-ignore-case 'smart-case))
  269. (ack-and-a-half-option "env" ack-and-a-half-use-environment))))
  270. (unless ack-and-a-half-ignore-case
  271. (push "-i" arguments))
  272. (unless regexp
  273. (push "--literal" arguments))
  274. arguments))
  275. (defun ack-and-a-half-string-replace (from to string &optional re)
  276. "Replace all occurrences of FROM with TO in STRING.
  277. All arguments are strings.
  278. When optional fourth argument is non-nil, treat the from as a regular expression."
  279. (let ((pos 0)
  280. (res "")
  281. (from (if re from (regexp-quote from))))
  282. (while (< pos (length string))
  283. (if (setq beg (string-match from string pos))
  284. (progn
  285. (setq res (concat res
  286. (substring string pos (match-beginning 0))
  287. to))
  288. (setq pos (match-end 0)))
  289. (progn
  290. (setq res (concat res (substring string pos (length string))))
  291. (setq pos (length string)))))
  292. res))
  293. (defun ack-and-a-half-shell-quote (string)
  294. "Wrap in single quotes, and quote existing single quotes to make shell safe."
  295. (concat "'" (ack-and-a-half-string-replace "'" "'\\''" string) "'"))
  296. (defun ack-and-a-half-run (directory regexp &rest arguments)
  297. "Run ack in DIRECTORY with ARGUMENTS."
  298. (setq default-directory
  299. (if directory
  300. (file-name-as-directory (expand-file-name directory))
  301. default-directory))
  302. (setq arguments (append ack-and-a-half-arguments
  303. (nconc (ack-and-a-half-arguments-from-options regexp)
  304. arguments)))
  305. (compilation-start (mapconcat 'identity (nconc (list ack-and-a-half-executable) arguments) " ")
  306. 'ack-and-a-half-mode))
  307. (defun ack-and-a-half-read-file (prompt choices)
  308. (if ido-mode
  309. (ido-completing-read prompt choices nil t)
  310. (require 'iswitchb)
  311. (with-no-warnings
  312. (let ((iswitchb-make-buflist-hook
  313. (lambda () (setq iswitchb-temp-buflist choices))))
  314. (iswitchb-read-buffer prompt nil t)))))
  315. (defun ack-and-a-half-list-files (directory &rest arguments)
  316. (with-temp-buffer
  317. (let ((default-directory directory))
  318. (when (eq 0 (apply 'call-process ack-and-a-half-executable nil t nil "-f" "--print0"
  319. arguments))
  320. (goto-char (point-min))
  321. (let ((beg (point-min))
  322. files)
  323. (while (re-search-forward "\0" nil t)
  324. (push (buffer-substring beg (match-beginning 0)) files)
  325. (setq beg (match-end 0)))
  326. files)))))
  327. (defun ack-and-a-half-version-string ()
  328. "Return the ack version string."
  329. (with-temp-buffer
  330. (call-process ack-executable nil t nil "--version")
  331. (goto-char (point-min))
  332. (re-search-forward " +")
  333. (buffer-substring (point) (point-at-eol))))
  334. ;;; Public interface ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  335. ;;;###autoload
  336. (defun ack-and-a-half (pattern &optional regexp directory)
  337. "Run ack.
  338. PATTERN is interpreted as a regular expression, iff REGEXP is non-nil. If
  339. called interactively, the value of REGEXP is determined by `ack-and-a-half-regexp-search'.
  340. A prefix arg toggles the behavior.
  341. DIRECTORY is the root directory. If called interactively, it is determined by
  342. `ack-and-a-half-project-root-file-patterns'. The user is only prompted, if
  343. `ack-and-a-half-prompt-for-directory' is set."
  344. (interactive (ack-and-a-half-interactive))
  345. (ack-and-a-half-run directory regexp (ack-and-a-half-shell-quote pattern)))
  346. ;;;###autoload
  347. (defun ack-and-a-half-same (pattern &optional regexp directory)
  348. "Run ack with --type matching the current `major-mode'.
  349. The types of files searched are determined by `ack-and-a-half-mode-type-alist' and
  350. `ack-and-a-half-mode-extension-alist'. If no type is configured, the buffer's
  351. file extension is used for the search.
  352. PATTERN is interpreted as a regular expression, iff REGEXP is non-nil. If
  353. called interactively, the value of REGEXP is determined by `ack-and-a-half-regexp-search'.
  354. A prefix arg toggles that value.
  355. DIRECTORY is the directory in which to start searching. If called
  356. interactively, it is determined by `ack-and-a-half-project-root-file-patterns`.
  357. The user is only prompted, if `ack-and-a-half-prompt-for-directory' is set.`"
  358. (interactive (ack-and-a-half-interactive))
  359. (let ((type (ack-and-a-half-type)))
  360. (if type
  361. (apply 'ack-and-a-half-run directory regexp (append type (list (ack-and-a-half-shell-quote pattern))))
  362. (ack-and-a-half pattern regexp directory))))
  363. ;;;###autoload
  364. (defun ack-and-a-half-find-file (&optional directory)
  365. "Prompt to find a file found by ack in DIRECTORY."
  366. (interactive (list (ack-and-a-half-read-dir)))
  367. (find-file (expand-file-name
  368. (ack-and-a-half-read-file
  369. "Find file: "
  370. (ack-and-a-half-list-files directory))
  371. directory)))
  372. ;;;###autoload
  373. (defun ack-and-a-half-find-file-same (&optional directory)
  374. "Prompt to find a file found by ack in DIRECTORY."
  375. (interactive (list (ack-and-a-half-read-dir)))
  376. (find-file (expand-file-name
  377. (ack-and-a-half-read-file
  378. "Find file: "
  379. (apply 'ack-and-a-half-list-files directory (ack-and-a-half-type)))
  380. directory)))
  381. ;;; End ack-and-a-half.el ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  382. (provide 'ack-and-a-half)