Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 681 lines (607 sloc) 24.262 kB
fb81b9d @nschum Initial import
authored
1 ;;; full-ack.el --- a front-end for ack
2 ;;
3 ;; Copyright (C) 2009 Nikolaj Schumacher
4 ;;
5 ;; Author: Nikolaj Schumacher <bugs * nschum de>
064eb77 @nschum Bumped version to 0.2.1.
authored
6 ;; Version: 0.2.1
fb81b9d @nschum Initial import
authored
7 ;; Keywords: tools, matching
8 ;; URL: http://nschum.de/src/emacs/full-ack/
9 ;; Compatibility: GNU Emacs 22.x, GNU Emacs 23.x
10 ;;
11 ;; This file is NOT part of GNU Emacs.
12 ;;
13 ;; This program is free software; you can redistribute it and/or
14 ;; modify it under the terms of the GNU General Public License
15 ;; as published by the Free Software Foundation; either version 2
16 ;; of the License, or (at your option) any later version.
17 ;;
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22 ;;
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ;;
26 ;;; Commentary:
27 ;;
7ae1238 @nschum Added description of ack.
authored
28 ;; ack is a tool like grep, aimed at programmers with large trees of
29 ;; heterogeneous source code.
30 ;; It is aailable at <http://betterthangrep.com/>.
31 ;;
bb5f8e3 @nschum Added installation info.
authored
32 ;; Add the following to your .emacs:
33 ;;
34 ;; (add-to-list 'load-path "/path/to/full-ack")
35 ;; (autoload 'ack-same "full-ack" nil t)
36 ;; (autoload 'ack "full-ack" nil t)
58d9bbd @nschum Added missing autoload instructions.
authored
37 ;; (autoload 'ack-find-same-file "full-ack" nil t)
38 ;; (autoload 'ack-find-file "full-ack" nil t)
bb5f8e3 @nschum Added installation info.
authored
39 ;;
fbd0a2b @nschum Added usage info.
authored
40 ;; Run `ack' to search for all files and `ack-same' to search for files of the
41 ;; same type as the current buffer.
42 ;;
43 ;; `next-error' and `previous-error' can be used to jump to the matches.
44 ;;
af37280 @nschum Added file finding.
authored
45 ;; `ack-find-file' and `ack-find-same-file' use ack to list the files in the
46 ;; current project. It's a convenient, though slow, way of finding files.
47 ;;
fb81b9d @nschum Initial import
authored
48 ;;; Change Log:
49 ;;
064eb77 @nschum Bumped version to 0.2.1.
authored
50 ;; 2009-04-13 (0.2.1)
3c54981 @nschum Added ack-next-match and ack-previous-match.
authored
51 ;; Added `ack-next-match' and `ack-previous-match'.
7e997dc @nschum Let Mouse clicking move next error position.
authored
52 ;; Fixed mouse clicking and let it move next-error position.
0289462 @nschum Fixed mouse clicking.
authored
53 ;;
01d5946 @nschum Bumped version to 0.2.
authored
54 ;; 2009-04-06 (0.2)
7761fd8 @nschum Added 'unless-guessed value for ack-prompt-for-directory.
authored
55 ;; Added 'unless-guessed value for `ack-prompt-for-directory'.
af37280 @nschum Added file finding.
authored
56 ;; Added `ack-list-files', `ack-find-file' and `ack-find-same-file'.
c5966f7 @nschum Fixed regexp toggling.
authored
57 ;; Fixed regexp toggling.
58 ;;
c4b9395 @nschum Bumped version to 0.1.
authored
59 ;; 2009-04-05 (0.1)
fb81b9d @nschum Initial import
authored
60 ;; Initial release.
61 ;;
62 ;;; Code:
63
64 (eval-when-compile (require 'cl))
65 (require 'compile)
66
67 (add-to-list 'debug-ignored-errors
68 "^Moved \\(back before fir\\|past la\\)st match$")
69 (add-to-list 'debug-ignored-errors "^File .* not found$")
70
71 (defgroup full-ack nil
72 "A front-end for ack."
73 :group 'tools
74 :group 'matching)
75
76 (defcustom ack-executable (executable-find "ack")
77 "*The location of the ack executable."
78 :group 'full-ack
79 :type 'file)
80
81 (defcustom ack-arguments nil
82 "*The arguments to use when running ack."
83 :group 'full-ack
84 :type '(repeat (string)))
85
86 (defcustom ack-mode-type-alist nil
9fefde4 @nschum Split types and extensions into two lists.
authored
87 "*Matches major modes to searched file types.
fb81b9d @nschum Initial import
authored
88 This overrides values in `ack-mode-default-type-alist'. The car in each
64f63c2 @nschum Allow multiple types.
authored
89 list element is a major mode, the rest are strings representing values of
9fefde4 @nschum Split types and extensions into two lists.
authored
90 the --type argument used by `ack-same'."
fb81b9d @nschum Initial import
authored
91 :group 'full-ack
92 :type '(repeat (cons (symbol :tag "Major mode")
64f63c2 @nschum Allow multiple types.
authored
93 (repeat (string :tag "ack type")))))
fb81b9d @nschum Initial import
authored
94
9fefde4 @nschum Split types and extensions into two lists.
authored
95 (defcustom ack-mode-extension-alist nil
96 "*Matches major modes to searched file extensions.
97 This overrides values in `ack-mode-default-extension-alist'. The car in
98 each list element is a major mode, the rest is a list of file extensions
99 that that should be searched in addition to the type defined in
100 `ack-mode-type-alist' by `ack-same'."
101 :group 'full-ack
102 :type '(repeat (cons (symbol :tag "Major mode")
103 (repeat :tag "File extensions"
104 (string :tag "extension")))))
105
fb81b9d @nschum Initial import
authored
106 (defcustom ack-ignore-case 'smart
107 "*Determines whether `ack' ignores the search case.
108 Special value 'smart enables ack option \"smart-case\"."
109 :group 'full-ackk
110 :type '(choice (const :tag "Case sensitive" nil)
111 (const :tag "Smart" 'smart)
112 (const :tag "Ignore case" t)))
113
114 (defcustom ack-search-regexp t
115 "*Determines whether `ack' should default to regular expression search.
116 Giving a prefix arg to `ack' toggles this option."
117 :group 'full-ack
118 :type '(choice (const :tag "Literal" nil)
119 (const :tag "Regular expression" t)))
120
121 (defcustom ack-display-buffer t
122 "*Determines whether `ack' should display the result buffer.
123 Special value 'after means display the buffer only after a successful search."
124 :group 'full-ack
125 :type '(choice (const :tag "Don't display" nil)
126 (const :tag "Display immediately" t)
127 (const :tag "Display when done" 'after)))
128
129 (defcustom ack-context 2
130 "*The number of context lines for `ack'"
131 :group 'full-ack
132 :type 'integer)
133
134 (defcustom ack-heading t
135 "*Determines whether `ack' results should be grouped by file."
136 :group 'full-ack
137 :type '(choice (const :tag "No heading" nil)
138 (const :tag "Heading" t)))
139
140 (defcustom ack-use-environment t
141 "*Determines whether `ack' should use access .ackrc and ACK_OPTIONS."
142 :group 'full-ack
143 :type '(choice (const :tag "Ignore environment" nil)
144 (const :tag "Use environment" t)))
145
146 (defcustom ack-root-directory-functions '(ack-guess-project-root)
147 "*A list of functions used to find the ack base directory.
148 These functions are called until one returns a directory. If successful,
149 `ack' is run from that directory instead of `default-directory'. The
150 directory is verified by the user depending on `ack-promtp-for-directory'."
151 :group 'full-ack
152 :type '(repeat function))
153
154 (defcustom ack-project-root-file-patterns
7aa49a5 @nschum Added some revision control directory names for root detection.
authored
155 '(".project\\'" ".xcodeproj\\'" ".sln\\'" "\\`Project.ede\\'"
156 "\\`.git\\'" "\\`.bzr\\'" "\\`_darcs\\'" "\\`.hg\\'")
fb81b9d @nschum Initial import
authored
157 "A list of project file patterns for `ack-guess-project-root'.
158 Each element is a regular expression. If a file matching either element is
159 found in a directory, that directory is assumed to be the project root by
160 `ack-guess-project-root'."
161 :group 'full-ack
162 :type '(repeat (string :tag "Regular expression")))
163
7761fd8 @nschum Added 'unless-guessed value for ack-prompt-for-directory.
authored
164 (defcustom ack-prompt-for-directory nil
fb81b9d @nschum Initial import
authored
165 "*Determines whether `ack' asks the user for the root directory.
7761fd8 @nschum Added 'unless-guessed value for ack-prompt-for-directory.
authored
166 If this is 'unless-guessed, the value determined by
167 `ack-root-directory-functions' is used without confirmation. If it is
168 nil, the directory is never confirmed."
fb81b9d @nschum Initial import
authored
169 :group 'full-ack
170 :type '(choice (const :tag "Don't prompt" nil)
7761fd8 @nschum Added 'unless-guessed value for ack-prompt-for-directory.
authored
171 (const :tag "Don't Prompt when guessed " unless-guessed)
fb81b9d @nschum Initial import
authored
172 (const :tag "Prompt" t)))
173
174 ;;; faces ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
175
176 (defface ack-separator
177 '((default (:foreground "gray50")))
178 "*Face for the group separator \"--\" in `ack' output."
179 :group 'full-ack)
180
181 (defface ack-file
182 '((((background dark)) (:foreground "green1"))
183 (((background light)) (:foreground "green4")))
184 "*Face for file names in `ack' output."
185 :group 'full-ack)
186
187 (defface ack-line
188 '((((background dark)) (:foreground "LightGoldenrod"))
189 (((background dark)) (:foreground "DarkGoldenrod")))
190 "*Face for line numbers in `ack' output."
191 :group 'full-ack)
192
193 (defface ack-match
194 '((default (:foreground "black"))
195 (((background dark)) (:background "yellow"))
196 (((background light)) (:background "yellow")))
197 "*Face for matched text in `ack' output."
198 :group 'full-ack)
199
200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
201
202 (defconst ack-mode-default-type-alist
203 ;; Some of these names are guessed. More should be constantly added.
64f63c2 @nschum Allow multiple types.
authored
204 '((actionscript-mode "actionscript")
205 (LaTeX-mode "tex")
206 (TeX-mode "tex")
207 (asm-mode "asm")
208 (batch-file-mode "batch")
209 (c++-mode "cpp")
210 (c-mode "cc")
211 (cfmx-mode "cfmx")
212 (cperl-mode "perl")
213 (csharp-mode "csharp")
214 (css-mode "css")
215 (emacs-lisp-mode "elisp")
216 (erlang-mode "erlang")
217 (espresso-mode "js")
218 (fortran-mode "fortran")
219 (haskell-mode "haskell")
220 (hexl-mode "binary")
221 (html-mode "html")
222 (java-mode "java")
223 (javascript-mode "js")
224 (jde-mode "java")
225 (js2-mode "js")
226 (jsp-mode "jsp")
227 (latex-mode "tex")
228 (lisp-mode "lisp")
229 (lua-mode "lua")
230 (makefile-mode "make")
231 (mason-mode "mason")
232 (nxml-mode "xml")
233 (objc-mode "objc" "objcpp")
234 (ocaml-mode "ocaml")
235 (parrot-mode "parrot")
236 (perl-mode "perl")
237 (php-mode "php")
238 (plone-mode "plone")
239 (python-mode "python")
240 (ruby-mode "ruby")
241 (scheme-mode "scheme")
242 (shell-script-mode "shell")
243 (skipped-mode "skipped")
244 (smalltalk-mode "smalltalk")
245 (sql-mode "sql")
246 (tcl-mode "tcl")
247 (tex-mode "tex")
248 (text-mode "text")
249 (tt-mode "tt")
250 (vb-mode "vb")
251 (vim-mode "vim")
252 (xml-mode "xml")
253 (yaml-mode "yaml"))
fb81b9d @nschum Initial import
authored
254 "Default values for `ack-mode-type-alist', which see.")
255
9fefde4 @nschum Split types and extensions into two lists.
authored
256 (defconst ack-mode-default-extension-alist
257 '((d-mode "d"))
258 "Default values for `ack-mode-extension-alist', which see.")
259
1a6d137 @nschum Let `ack-same' search the current file name extension if unknown.
authored
260 (defun ack-create-type (extensions)
261 (list "--type-set"
262 (concat "full-ack-custom-type=" (mapconcat 'identity extensions ","))
263 "--type" "full-ack-custom-type"))
264
fb81b9d @nschum Initial import
authored
265 (defun ack-type-for-major-mode (mode)
266 "Return the --type and --type-set arguments for major mode MODE."
64f63c2 @nschum Allow multiple types.
authored
267 (let ((types (cdr (or (assoc mode ack-mode-type-alist)
268 (assoc mode ack-mode-default-type-alist))))
9fefde4 @nschum Split types and extensions into two lists.
authored
269 (ext (cdr (or (assoc mode ack-mode-extension-alist)
64f63c2 @nschum Allow multiple types.
authored
270 (assoc mode ack-mode-default-extension-alist))))
271 result)
272 (dolist (type types)
273 (push type result)
274 (push "--type" result))
9fefde4 @nschum Split types and extensions into two lists.
authored
275 (if ext
64f63c2 @nschum Allow multiple types.
authored
276 (if types
277 `("--type-add" ,(concat (car types)
278 "=" (mapconcat 'identity ext ","))
279 . ,result)
1a6d137 @nschum Let `ack-same' search the current file name extension if unknown.
authored
280 (ack-create-type ext))
64f63c2 @nschum Allow multiple types.
authored
281 result)))
fb81b9d @nschum Initial import
authored
282
283 ;;; root ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
284
285 (defun ack-guess-project-root ()
286 "A function to guess the project root directory.
287 This can be used in `ack-root-directory-functions'."
288 (catch 'root
82897ff @nschum Start search in default-directory if not a file.
authored
289 (let ((dir (expand-file-name (if buffer-file-name
290 (file-name-directory buffer-file-name)
291 default-directory)))
151fb37 @nschum Made root file search shorter by using a merged pattern.
authored
292 (pattern (mapconcat 'identity ack-project-root-file-patterns "\\|")))
fb81b9d @nschum Initial import
authored
293 (while (not (equal dir "/"))
151fb37 @nschum Made root file search shorter by using a merged pattern.
authored
294 (when (directory-files dir nil pattern t)
295 (throw 'root dir))
fb81b9d @nschum Initial import
authored
296 (setq dir (file-name-directory (directory-file-name dir)))))))
297
298 ;;; process ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
299
300 (defvar ack-buffer-name "*ack*")
301
302 (defvar ack-process nil)
303
304 (defun ack-count-matches ()
305 "Count the matches printed by `ack' in the current buffer."
306 (let ((c 0)
307 (beg (point-min)))
308 (setq beg (next-single-char-property-change beg 'ack-match))
309 (while (< beg (point-max))
310 (when (get-text-property beg 'ack-match)
311 (incf c))
312 (setq beg (next-single-char-property-change beg 'ack-match)))
313 c))
314
315 (defun ack-sentinel (proc result)
316 (when (eq (process-status proc) 'exit)
317 (with-current-buffer (process-buffer proc)
318 (let ((c (ack-count-matches)))
9b2281d @nschum Kill buffer unless successful.
authored
319 (if (> c 0)
320 (when (eq ack-display-buffer 'after)
321 (display-buffer (current-buffer)))
322 (kill-buffer (current-buffer)))
a109bb2 @nschum Pluralize match message correctly.
authored
323 (message "Ack finished with %d match%s" c (if (eq c 1) "" "es"))))))
fb81b9d @nschum Initial import
authored
324
325 (defun ack-filter (proc output)
326 (let ((buffer (process-buffer proc))
327 (inhibit-read-only t)
328 beg)
329 (if (buffer-live-p buffer)
330 (with-current-buffer buffer
331 (save-excursion
332 (goto-char (setq beg (point-max)))
333 (insert output)
334 ;; Error properties are done by font-lock.
335 (font-lock-fontify-region beg (point-max))))
336 (ack-abort))))
337
338 (defun ack-abort ()
339 "Abort the running `ack' process."
340 (interactive)
341 (when (processp ack-process)
342 (delete-process ack-process)))
343
344 (defsubst ack-option (name enabled)
345 (format "--%s%s" (if enabled "" "no") name))
346
c5966f7 @nschum Fixed regexp toggling.
authored
347 (defun ack-arguments-from-options (regexp)
fb81b9d @nschum Initial import
authored
348 (let ((arguments (list "--color"
349 (ack-option "smart-case" (eq ack-ignore-case 'smart))
350 (ack-option "heading" ack-heading)
351 (ack-option "env" ack-use-environment))))
352 (unless ack-ignore-case
353 (push "-i" arguments))
c5966f7 @nschum Fixed regexp toggling.
authored
354 (unless regexp
fb81b9d @nschum Initial import
authored
355 (push "--literal" arguments))
356 (push (format "--context=%d" ack-context) arguments)
357 arguments))
358
c5966f7 @nschum Fixed regexp toggling.
authored
359 (defun ack-run (directory regexp &rest arguments)
fb81b9d @nschum Initial import
authored
360 "Run ack in DIRECTORY with ARGUMENTS."
361 (ack-abort)
362 (setq directory
363 (if directory
364 (file-name-as-directory (expand-file-name directory))
365 default-directory))
366 (setq arguments (append ack-arguments
c5966f7 @nschum Fixed regexp toggling.
authored
367 (nconc (ack-arguments-from-options regexp)
fb81b9d @nschum Initial import
authored
368 arguments)))
369 (let ((buffer (get-buffer-create ack-buffer-name))
370 (inhibit-read-only t)
371 (default-directory directory))
372 (setq next-error-last-buffer buffer)
373 (with-current-buffer buffer
374 (erase-buffer)
375 (ack-mode)
376 (setq buffer-read-only t
377 default-directory directory)
378 (font-lock-fontify-buffer)
379 (when (eq ack-display-buffer t)
380 (display-buffer (current-buffer))))
381 (setq ack-process
382 (apply 'start-process "ack" buffer ack-executable arguments))
383 (set-process-sentinel ack-process 'ack-sentinel)
384 (set-process-query-on-exit-flag ack-process nil)
385 (set-process-filter ack-process 'ack-filter)))
386
62c5ecb @nschum Added ack-version-string.
authored
387 (defun ack-version-string ()
388 "Return the ack version string."
389 (with-temp-buffer
390 (call-process ack-executable nil t nil "--version")
391 (goto-char (point-min))
392 (re-search-forward " +")
393 (buffer-substring (point) (point-at-eol))))
394
af37280 @nschum Added file finding.
authored
395 (defun ack-list-files (directory &rest arguments)
396 (with-temp-buffer
397 (let ((default-directory directory))
398 (when (eq 0 (apply 'call-process ack-executable nil t nil "-f" "--print0"
399 arguments))
400 (goto-char (point-min))
401 (let ((beg (point-min))
402 files)
403 (while (re-search-forward "\0" nil t)
404 (push (buffer-substring beg (match-beginning 0)) files)
405 (setq beg (match-end 0)))
406 files)))))
407
fb81b9d @nschum Initial import
authored
408 ;;; commands ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
409
410 (defvar ack-directory-history nil
411 "Directories recently searched with `ack'.")
412 (defvar ack-literal-history nil
413 "Strings recently searched for with `ack'.")
414 (defvar ack-regexp-history nil
415 "Regular expressions recently searched for with `ack'.")
416
417 (defsubst ack-read (regexp)
418 (read-from-minibuffer (if regexp "ack pattern: " "ack literal search: ")
419 nil nil nil
420 (if regexp 'ack-regexp-history 'ack-literal-history)))
421
a72c81f @nschum Extracted ack-read-dir.
authored
422 (defun ack-read-dir ()
423 (let ((dir (run-hook-with-args-until-success 'ack-root-directory-functions)))
424 (if ack-prompt-for-directory
7761fd8 @nschum Added 'unless-guessed value for ack-prompt-for-directory.
authored
425 (if (and dir (eq ack-prompt-for-directory 'unless-guessed))
426 dir
427 (read-directory-name "Directory: " dir dir t))
428 (or dir
429 (and buffer-file-name (file-name-directory buffer-file-name))
430 default-directory))))
a72c81f @nschum Extracted ack-read-dir.
authored
431
fb81b9d @nschum Initial import
authored
432 (defsubst ack-xor (a b)
433 (if a (not b) b))
434
435 (defun ack-interactive ()
436 "Return the (interactive) arguments for `ack' and `ack-same'"
a72c81f @nschum Extracted ack-read-dir.
authored
437 (let ((regexp (ack-xor current-prefix-arg ack-search-regexp)))
fb81b9d @nschum Initial import
authored
438 (list (ack-read regexp)
439 regexp
a72c81f @nschum Extracted ack-read-dir.
authored
440 (ack-read-dir))))
fb81b9d @nschum Initial import
authored
441
af37280 @nschum Added file finding.
authored
442 (defun ack-type ()
443 (or (ack-type-for-major-mode major-mode)
444 (when buffer-file-name
445 (ack-create-type (list (file-name-extension buffer-file-name))))))
446
fb81b9d @nschum Initial import
authored
447 ;;;###autoload
448 (defun ack-same (pattern &optional regexp directory)
449 "Run ack with --type matching the current `major-mode'.
1a6d137 @nschum Let `ack-same' search the current file name extension if unknown.
authored
450 The types of files searched are determined by `ack-mode-type-alist' and
451 `ack-mode-extension-alist'. If no type is configured the buffer's file
452 extension is used for the search.
fb81b9d @nschum Initial import
authored
453 PATTERN is interpreted as a regular expression, iff REGEXP is non-nil. If
454 called interactively, the value of REGEXP is determined by `ack-search-regexp'.
455 A prefix arg toggles that value.
456 DIRECTORY is the root directory. If called interactively, it is determined by
457 `ack-project-root-file-patterns'. The user is only prompted, if
458 `ack-prompt-for-directory' is set."
459 (interactive (ack-interactive))
af37280 @nschum Added file finding.
authored
460 (let ((type (ack-type)))
fb81b9d @nschum Initial import
authored
461 (if type
68d9be4 @nschum Added missing argument in ack-same.
authored
462 (apply 'ack-run directory regexp (append type (list pattern)))
fb81b9d @nschum Initial import
authored
463 (ack pattern regexp directory))))
464
465 ;;;###autoload
466 (defun ack (pattern &optional regexp directory)
467 "Run ack.
468 PATTERN is interpreted as a regular expression, iff REGEXP is non-nil. If
469 called interactively, the value of REGEXP is determined by `ack-search-regexp'.
470 A prefix arg toggles that value.
471 DIRECTORY is the root directory. If called interactively, it is determined by
472 `ack-project-root-file-patterns'. The user is only prompted, if
473 `ack-prompt-for-directory' is set."
474 (interactive (ack-interactive))
7acacb3 @nschum Removed wrong --all-types argument.
authored
475 (ack-run directory regexp pattern))
fb81b9d @nschum Initial import
authored
476
af37280 @nschum Added file finding.
authored
477 (defun ack-read-file (prompt choices)
478 (if ido-mode
479 (ido-completing-read prompt choices nil t)
480 (require 'iswitchb)
481 (with-no-warnings
482 (let ((iswitchb-make-buflist-hook
483 (lambda () (setq iswitchb-temp-buflist choices))))
484 (iswitchb-read-buffer prompt nil t)))))
485
486 ;;;###autoload
487 (defun ack-find-same-file (&optional directory)
488 "Prompt to find a file found by ack in DIRECTORY."
a72c81f @nschum Extracted ack-read-dir.
authored
489 (interactive (list (ack-read-dir)))
af37280 @nschum Added file finding.
authored
490 (find-file (expand-file-name
491 (ack-read-file "Find file: "
492 (apply 'ack-list-files directory (ack-type)))
493 directory)))
494
495 ;;;###autoload
496 (defun ack-find-file (&optional directory)
497 "Prompt to find a file found by ack in DIRECTORY."
a72c81f @nschum Extracted ack-read-dir.
authored
498 (interactive (list (ack-read-dir)))
af37280 @nschum Added file finding.
authored
499 (find-file (expand-file-name (ack-read-file "Find file: "
500 (ack-list-files directory))
501 directory)))
502
fb81b9d @nschum Initial import
authored
503 ;;; text utilities ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
504
505 (defun ack-visible-distance (beg end)
506 "Determine the number of visible characters between BEG and END."
507 (let ((offset 0)
508 next)
509 ;; Subtract invisible text
510 (when (get-text-property beg 'invisible)
511 (setq beg (next-single-property-change beg 'invisible)))
512 (while (and beg (< beg end))
513 (if (setq next (next-single-property-change beg 'invisible))
514 (setq offset (+ offset (- (min next end) beg))
515 beg (next-single-property-change next 'invisible))
516 (setq beg nil)))
517 offset))
518
519 (defun ack-previous-property-value (property pos)
520 "Find the value of PROPERTY at or somewhere before POS."
521 (or (get-text-property pos property)
522 (when (setq pos (previous-single-property-change pos property))
523 (get-text-property (1- pos) property))))
524
525 (defun ack-property-beg (pos property)
526 "Move to the first char of consecutive sequence with PROPERTY set."
527 (when (get-text-property pos property)
528 (if (or (eq pos (point-min))
529 (not (get-text-property (1- pos) property)))
530 pos
531 (previous-single-property-change pos property))))
532
533 (defun ack-property-end (pos property)
534 "Move to the last char of consecutive sequence with PROPERTY set."
535 (when (get-text-property pos property)
536 (if (or (eq pos (point-max))
537 (not (get-text-property (1+ pos) property)))
538 pos
539 (next-single-property-change pos property))))
540
541 ;;; next-error ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
542
543 (defvar ack-error-pos nil)
544 (make-variable-buffer-local 'ack-error-pos)
545
3c54981 @nschum Added ack-next-match and ack-previous-match.
authored
546 (defun ack-next-match (pos arg)
547 (interactive "d\np")
548 (setq arg (* 2 arg))
549 (unless (get-text-property pos 'ack-match)
550 (setq arg (1- arg)))
551 (assert (> arg 0))
552 (dotimes (i arg)
553 (setq pos (next-single-property-change pos 'ack-match))
554 (unless pos
555 (error "Moved past last match")))
556 (goto-char pos)
557 pos)
558
559 (defun ack-previous-match (pos arg)
560 (interactive "d\np")
561 (assert (> arg 0))
562 (dotimes (i (* 2 arg))
563 (setq pos (previous-single-property-change pos 'ack-match))
564 (unless pos
565 (error "Moved back before first match")))
566 (goto-char pos)
567 pos)
fb81b9d @nschum Initial import
authored
568
569 (defun ack-next-error-function (arg reset)
570 (when (or reset (null ack-error-pos))
571 (setq ack-error-pos (point-min)))
7e997dc @nschum Let Mouse clicking move next error position.
authored
572 (ack-find-match (if (<= arg 0)
3c54981 @nschum Added ack-next-match and ack-previous-match.
authored
573 (ack-previous-match ack-error-pos (- arg))
574 (ack-next-match ack-error-pos arg))))
fb81b9d @nschum Initial import
authored
575
576 (defun ack-create-marker (pos end &optional force)
577 (let ((file (ack-previous-property-value 'ack-file pos))
578 (line (ack-previous-property-value 'ack-line pos))
579 (offset (ack-visible-distance
580 (1+ (previous-single-property-change pos 'ack-line)) pos))
581 buffer)
582 (if force
583 (or (and file
584 line
585 (file-exists-p file)
586 (setq buffer (find-file-noselect file)))
587 (error "File <%s> not found" file))
588 (and file
589 line
590 (setq buffer (find-buffer-visiting file))))
591 (when buffer
592 (with-current-buffer buffer
593 (save-excursion
594 (goto-line (string-to-number line))
595 (copy-marker (+ (point) offset)))))))
596
597 (defun ack-find-match (pos)
598 "Jump to the match at POS."
0289462 @nschum Fixed mouse clicking.
authored
599 (interactive (list (let ((posn (event-start last-input-event)))
600 (set-buffer (window-buffer (posn-window posn)))
601 (posn-point posn))))
602 (when (setq pos (ack-property-beg pos 'ack-match))
603 (let ((marker (get-text-property pos 'ack-marker))
604 (msg (copy-marker pos))
605 (msg-end (ack-property-end pos 'ack-match))
606 (compilation-context-lines ack-context)
607 (inhibit-read-only t)
2640c0d @nschum Fixed end marker position.
authored
608 (end (make-marker)))
7e997dc @nschum Let Mouse clicking move next error position.
authored
609 (setq ack-error-pos pos)
610
611 (let ((bol (save-excursion (goto-char pos) (point-at-bol))))
612 (if overlay-arrow-position
613 (move-marker overlay-arrow-position bol)
614 (setq overlay-arrow-position (copy-marker bol))))
615
0289462 @nschum Fixed mouse clicking.
authored
616 (unless (and marker (marker-buffer marker))
617 (setq marker (ack-create-marker msg msg-end t))
618 (add-text-properties msg msg-end (list 'ack-marker marker)))
2640c0d @nschum Fixed end marker position.
authored
619 (set-marker end (+ marker (ack-visible-distance msg msg-end))
620 (marker-buffer marker))
0289462 @nschum Fixed mouse clicking.
authored
621 (compilation-goto-locus msg marker end)
622 (set-marker msg nil)
623 (set-marker end nil))))
fb81b9d @nschum Initial import
authored
624
625 ;;; ack-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
626
627 (defvar ack-mode-map
628 (let ((keymap (make-sparse-keymap)))
629 (define-key keymap [mouse-2] 'ack-find-match)
630 (define-key keymap "\C-m" 'ack-find-match)
3c54981 @nschum Added ack-next-match and ack-previous-match.
authored
631 (define-key keymap "n" 'ack-next-match)
632 (define-key keymap "p" 'ack-previous-match)
fb81b9d @nschum Initial import
authored
633 keymap))
634
635 (defvar ack-font-lock-keywords
636 `(("^--" . 'ack-separator)
637 ;; file and maybe line
638 ("^\\(\33\\[1;..m\\)\\(.*?\\)\\(\33\\[0m\\)\\([:-]\\([0-9]+\\)[:-]\\)?"
639 (1 '(face nil invisible t))
ed10f5f @nschum Fixed face specification error.
authored
640 (2 `(face ack-file
fb81b9d @nschum Initial import
authored
641 ack-file ,(match-string-no-properties 2)))
642 (3 '(face nil invisible t))
ed10f5f @nschum Fixed face specification error.
authored
643 (5 `(face ack-line
fb81b9d @nschum Initial import
authored
644 ack-line ,(match-string-no-properties 5))
645 nil 'optional))
646 ;; lines
647 ("^\\([0-9]+\\)[:-]"
ed10f5f @nschum Fixed face specification error.
authored
648 (1 `(face ack-line
fb81b9d @nschum Initial import
authored
649 ack-line ,(match-string-no-properties 1))))
650 ;; matches
651 ("\\(\33\\[30;..m\\)\\(.*?\\)\\(\33\\[0m\\)"
652 (1 '(face nil invisible t))
ed10f5f @nschum Fixed face specification error.
authored
653 (0 `(face ack-match
fb81b9d @nschum Initial import
authored
654 ack-marker ,(ack-create-marker (match-beginning 2) (match-end 2))
655 ack-match t
ed10f5f @nschum Fixed face specification error.
authored
656 mouse-face highlight
fb81b9d @nschum Initial import
authored
657 follow-link t))
658 (3 '(face nil invisible t)))
659 ;; noise
660 ("\\(\33\\[\\(0m\\|K\\)\\)"
661 (0 '(face nil invisible t)))))
662
663 (define-derived-mode ack-mode nil "ack"
664 "Major mode for ack output."
4c7dece @nschum Removed accidental string and comment fontification.
authored
665 font-lock-defaults
666 (setq font-lock-defaults
667 (list ack-font-lock-keywords t))
fb81b9d @nschum Initial import
authored
668 (set (make-local-variable 'font-lock-extra-managed-props)
669 '(mouse-face follow-link ack-line ack-file ack-marker ack-match))
670 (make-local-variable 'overlay-arrow-position)
671 (set (make-local-variable 'overlay-arrow-string) "")
672
673 (font-lock-fontify-buffer)
674 (use-local-map ack-mode-map)
675
676 (setq next-error-function 'ack-next-error-function
677 ack-error-pos nil))
678
679 (provide 'full-ack)
680 ;;; full-ack.el ends here
Something went wrong with that request. Please try again.