1 ;;; project-buffer-occur.el --- Occur functionality for Project Mode
3 ;; Author: Cedric Lallain <kandjar76@hotmail.com>
5 ;; Keywords: occur project buffer makefile filesystem management
6 ;; Description: Occur Functionality for Project-Buffer-Mode
7 ;; Tested with: GNU Emacs 22.x and GNU Emacs 23.x
9 ;; This file is *NOT* part of GNU Emacs.
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2 of the License, or
14 ;; (at your option) any later version.
16 ;; This program 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.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, write to the Free Software
23 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 ;; This is an extension for project-buffer-mode.
31 ;; Provide a 'occur' like functionality for project-buffer-mode.
40 ;; Call the command `project-buffer-occur' in a project-buffer-mode buffer.
42 ;; The research will occur in all marked files; or in all files
43 ;; belonging to the current project if there are no files. Using the
44 ;; prefix argument, the research will be done in all files.
49 ;; Put the following lines in your .emacs:
51 ;; (eval-after-load "project-buffer-mode"
53 ;; (require 'project-buffer-occur)
54 ;; (define-key project-buffer-mode-map [(control ?f)] 'project-buffer-occur)))
59 ;; <RET> - goto-occurence
60 ;; o - goto-occurence other window
61 ;; v - display occurrence
62 ;; n - next occurence / prev search occurrence
63 ;; p - prev occurence / next search occurrence
64 ;; M-n - go to next file
65 ;; M-p - go to prev file
66 ;; C-n - go to next occurrence and display it
67 ;; C-p - go to the previous occurrence and display it
69 ;; g - refresh the research
70 ;; d - delete the current line
72 ;; ? - show brief help
77 ;; v1.0: First official release.
79 ;; - (goto-char (point-min)) was not working.
80 ;; - file-name weren't attached properly to the occurrences
81 ;; - non-existing files were stopping the research / they are now skipped.
85 (require 'project-buffer-mode
)
92 (defgroup project-buffer-occur nil
93 "An occur mode for project-buffer.")
98 ;; Global configuration variable:
102 (defvar project-buffer-occur-context-size
32
103 "Size of the context stored for each occurrence; to help retrieving the data after modification.")
106 (defface project-buffer-occur-file-line
107 '((((class color
) (background light
)) (:foreground
"blue"))
108 (((class color
) (background dark
)) (:foreground
"yellow")))
109 "Project buffer occur face used to highlight file line."
110 :group
'project-buffer-occur
)
113 (defface project-buffer-occur-line-number
114 '((((class color
) (background light
)) (:foreground
"red"))
115 (((class color
) (background dark
)) (:foreground
"cyan")))
116 "Project buffer occur face used to highlight line number."
117 :group
'project-buffer-occur
)
120 (defface project-buffer-occur-odd-matching-line
121 '((((class color
) (background light
)) (:foreground
"black"))
122 (((class color
) (background dark
)) (:foreground
"white")))
123 "Project buffer occur face used to highlight odd matching line."
124 :group
'project-buffer-occur
)
127 (defface project-buffer-occur-even-matching-line
128 '((((class color
) (background light
)) (:foreground
"gray60"))
129 (((class color
) (background dark
)) (:foreground
"gray")))
130 "Project buffer occur face used to highlight even matching line."
131 :group
'project-buffer-occur
)
134 (defface project-buffer-occur-highlight-matching-string
135 '((((class color
) (background light
)) (:background
"yellow"))
136 (((class color
) (background dark
)) (:background
"yellow")))
137 "Project buffer occur face used to highlight the matching string."
138 :group
'project-buffer-occur
)
141 (defcustom project-buffer-occur-mode-hook nil
142 "Post `project-buffer-occur-mode' initialization hook."
144 :group
'project-buffer-occur
)
153 (defvar project-buffer-occur-saved-project-buffer nil
)
154 (defvar project-buffer-occur-saved-regexp nil
)
163 ;; Define the key mapping for the spu mode:
164 (defvar project-buffer-occur-map
165 (let ((project-buffer-occur-map (make-keymap)))
166 (define-key project-buffer-occur-map
[return] 'project-buffer-occur-goto-occurrence)
167 (define-key project-buffer-occur-map [?o] 'project-buffer-occur-goto-occurrence-other-window)
168 (define-key project-buffer-occur-map [?v] 'project-buffer-occur-view-occurrence)
169 (define-key project-buffer-occur-map [?n] 'project-buffer-occur-next-occurrence)
170 (define-key project-buffer-occur-map [?p] 'project-buffer-occur-previous-occurrence)
171 (define-key project-buffer-occur-map [(meta ?n)] 'project-buffer-occur-next-file)
172 (define-key project-buffer-occur-map [(meta ?p)] 'project-buffer-occur-previous-file)
173 (define-key project-buffer-occur-map [(control ?n)] 'project-buffer-occur-view-next-occurrence)
174 (define-key project-buffer-occur-map [(control ?p)] 'project-buffer-occur-view-previous-occurrence)
175 (define-key project-buffer-occur-map [?d] 'project-buffer-occur-delete-line)
176 (define-key project-buffer-occur-map [?q] 'quit-window)
177 (define-key project-buffer-occur-map [?r] 'project-buffer-occur-rename-buffer)
178 (define-key project-buffer-occur-map [?g] 'project-buffer-occur-refresh)
179 (define-key project-buffer-occur-map [??] 'project-buffer-occur-help)
180 (define-key project-buffer-occur-map [mouse-2] 'project-buffer-occur-mouse-find-file)
181 project-buffer-occur-map))
189 (defun project-buffer-occur-clear-overlays()
190 "Clear the project-buffer-occur overlays from the current buffer."
191 (let ((ovl-lists (overlay-lists)))
192 (mapcar (lambda (overlay)
193 (when (overlay-get overlay 'project-buffer-occur-tag)
194 (delete-overlay overlay)))
196 (append (car ovl-lists) (cdr ovl-lists))))))
199 (defun project-buffer-occur-get-and-clear-occur-buffer()
200 "Retrieve the occur buffer and returns it.
201 If the buffer exists; the buffer is cleared. If the buffer
202 doesn't exist, a new buffer is created and initialized with
203 project-buffer-occur-major-mode."
204 (let ((buffer (get-buffer-create "*Project-Buffer-Occur*")))
205 (with-current-buffer buffer
206 (let ((inhibit-read-only t))
207 (project-buffer-occur-clear-overlays)
209 (project-buffer-occur-mode))
213 (defun project-buffer-occur-add-occurrence(file occurrence occurrence-num regexp)
214 "Add an OCCURRENCE from FILE in the buffer.
216 FILE should be the file in which the occurrence has been found,
217 OCCURRENCE is a list of (#line matching-line before-string after-string).
218 OCCURRENCE-NUM represents the OCCURENCE-NUM'th occurrence found in FILE"
220 (let ((occ-line-num (car occurrence))
221 (occ-line-str (nth 1 occurrence))
222 (occ-before-str (nth 2 occurrence))
223 (occ-after-str (nth 3 occurrence))
225 (cur-line (line-number-at-pos (point))))
226 (insert (propertize (format "%8i:" occ-line-num)
228 'mouse-face 'highlight
229 'face 'project-buffer-occur-line-number))
231 (insert (propertize occ-line-str
233 'mouse-face 'highlight
234 'face (if (oddp occurrence-num)
235 'project-buffer-occur-odd-matching-line
236 'project-buffer-occur-even-matching-line)))
237 (when (not (= (point) (point-at-bol)))
240 ;; Highlight matching string:
241 (goto-char start-pos)
242 (forward-char 10) ; skip the line number
243 (while (re-search-forward regexp nil t)
244 (let ((overlay (make-overlay (match-beginning 0) (match-end 0))))
245 (overlay-put overlay 'face 'project-buffer-occur-highlight-matching-string)
246 (overlay-put overlay 'project-buffer-occur-tag t)
249 ;; Fix the indentation:
250 (goto-char (point-max))
252 (while (not (= cur-line (line-number-at-pos (point))))
253 (goto-char (point-at-bol))
254 (insert (propertize (make-string 10 32)
256 'mouse-face 'highlight))
260 (let ((overlay (make-overlay start-pos (point-max))))
261 (overlay-put overlay 'project-buffer-occur-tag t)
262 (overlay-put overlay 'project-buffer-occur-context (list file occurrence regexp)))
263 (goto-char (point-max))))
266 (defun project-buffer-occur-collect-occurrences(regexp)
267 "Create a list of occurrences by searching REGEXP in the current buffer.
269 The return value is a list of ( line# matching-line before-string
270 after-string ). This function doesn't save the position and
271 assume the position will be saved and restored by the caller if
273 (goto-char (point-min))
285 (setq next-start (re-search-forward regexp nil t))
287 ;; Collect the data for this occcurrence:
288 ;; consider using: jit-lock-fontify-now! To get colors on the line...
289 (setq occ-beg (match-beginning 0))
290 (setq occ-end (match-end 0))
292 (setq occ-bol (point-at-bol))
293 (setq occ-line-num (line-number-at-pos))
295 (setq occ-eol (point-at-eol))
296 (setq occ-line-str (buffer-substring-no-properties occ-bol occ-eol))
297 (setq occ-after-str (and (>= (- (point-max) occ-eol) project-buffer-occur-context-size)
298 (buffer-substring-no-properties occ-eol (+ occ-eol project-buffer-occur-context-size))))
299 (setq occ-before-str (and (>= (- occ-bol (point-min)) project-buffer-occur-context-size)
300 (buffer-substring-no-properties (- occ-bol project-buffer-occur-context-size) occ-bol)))
301 ;; Add the occurrence to the list unless it occurs on the same line.
302 (unless (eq occ-line-num (car (car occurrences)))
303 (setq occurrences (cons (list occ-line-num occ-line-str occ-before-str occ-after-str)
306 (goto-char next-start)
307 (setq next-start (re-search-forward regexp nil t)))
308 (reverse occurrences))))
311 (defun project-buffer-occur-research(project-file-name file-path project-name regexp occur-buffer)
312 "Research REGEXP in FILE-PATH and fill OCCUR-BUFFER with the
313 different occurences found.
314 PROJECT-FILE-NAME and PROJECT-NAME are ignored."
316 (message "Project '%s' -- Searching in '%s'" project-name file-path)
317 ;; Collect all occurrences in this file:
318 (let ((file-buf (get-file-buffer file-path)))
320 (with-current-buffer file-buf
322 (setq occurrences (project-buffer-occur-collect-occurrences regexp))))
323 (when (file-exists-p file-path)
325 (insert-file-contents file-path)
326 (setq occurrences (project-buffer-occur-collect-occurrences regexp))))))
328 ;; Then populate the occurr buffer with it:
330 (with-current-buffer occur-buffer
331 (let ((inhibit-read-only t))
332 (goto-char (point-max))
333 (let ((start-pos (point)))
334 (insert (propertize (format "%i occurrence%s found in %s/%s"
336 (if (= 1 (length occurrences)) "" "s")
340 'mouse-face 'highlight
341 'face 'project-buffer-occur-file-line))
342 (let ((overlay (make-overlay start-pos (point))))
343 (overlay-put overlay 'project-buffer-occur-tag t)
344 (overlay-put overlay 'project-buffer-occur-context (list file-path nil regexp))))
348 (let ((occurrence (pop occurrences)))
349 (project-buffer-occur-add-occurrence file-path occurrence occ-count regexp)
350 (setq occ-count (1+ occ-count))))))))))
353 (defun project-buffer-occur-mode()
354 "Major mode for output from `project-buffer-occur'
357 \\{project-buffer-mode-map}"
358 (kill-all-local-variables)
359 (use-local-map project-buffer-occur-map)
361 (setq major-mode 'project-buffer-occur-mode)
362 (setq mode-name "pbm-occur")
364 (make-local-variable 'project-buffer-occur-saved-project-buffer)
365 (make-local-variable 'project-buffer-occur-saved-regexp)
367 (setq buffer-read-only t)
368 (setq buffer-undo-list t) ; disable undo recording
369 (run-mode-hooks 'project-buffer-occur-mode-hook))
372 (defun project-buffer-occur-goto-file(file &optional other-window)
373 "Go to the selected files."
375 (find-file-other-window file)
379 (defun project-buffer-occur-highlight-current(regexp start end)
380 "Highlight all occurrences of REGEXP between START adn END points"
386 (while (re-search-forward regexp end t)
387 (let ((overlay (make-overlay (match-beginning 0) (match-end 0))))
388 (overlay-put overlay 'face 'project-buffer-occur-highlight-matching-string)
389 (overlay-put overlay 'project-buffer-occur-tag t)
390 (setq ovl-list (cons overlay ovl-list)))))
392 (mapcar (lambda (overlay)
393 (delete-overlay overlay))
397 (defun project-buffer-occur-goto-matching-string(file line matching-line before-string after-string regexp &optional other-window)
398 "Go to an occurrence."
399 (let* ((buffer (find-file-noselect file))
400 (window (get-buffer-window buffer)))
402 (progn (select-window window)
405 (switch-to-buffer-other-window buffer)
406 (switch-to-buffer buffer)))
409 (goto-char (point-min))
410 (forward-line (1- line)))
413 (let ((cur-pt (point))
414 (end-pt (+ (point) (length matching-line) 1))
418 (when (and after-string (search-forward after-string nil t))
419 (setq aft-pt (match-beginning 0))
421 (when (and before-string (search-backward before-string nil t))
422 (setq bef-pt (match-end 0))
427 (if (search-forward matching-line aft-pt t)
428 (progn (goto-char (match-beginning 0))
429 (goto-char (point-at-bol)))
430 (if (re-search-forward regexp aft-pt t)
431 (progn (goto-char (match-beginning 0))
432 (goto-char (point-at-bol)))
433 (goto-char cur-pt))))
436 (if (search-forward matching-line aft-pt t)
437 (progn (goto-char (match-beginning 0))
438 (goto-char (point-at-bol)))
439 (if (re-search-forward regexp aft-pt t)
440 (progn (goto-char (match-beginning 0))
441 (goto-char (point-at-bol)))
442 (goto-char cur-pt))))
444 (if (search-forward matching-line end-pt t)
445 (progn (goto-char (match-beginning 0))
446 (goto-char (point-at-bol)))
447 (if (re-search-forward regexp end-pt t)
448 (progn (goto-char (match-beginning 0))
449 (goto-char (point-at-bol)))
450 (goto-char cur-pt))))
453 (if (search-forward matching-line end-pt t)
454 (progn (goto-char (match-beginning 0))
455 (goto-char (point-at-bol)))
456 (if (re-search-forward regexp end-pt t)
457 (progn (goto-char (match-beginning 0))
458 (goto-char (point-at-bol)))
459 (if (search-forward matching-line nil t)
460 (progn (goto-char (match-beginning 0))
461 (goto-char (point-at-bol)))
462 (if (search-backward matching-line nil t)
463 (progn (goto-char (match-beginning 0))
464 (goto-char (point-at-bol)))
465 (goto-char cur-pt))))))))))
468 (defun project-buffer-occur-goto-occurrence-at-pos(pos other-window)
469 "Go to the occurence found at POS."
471 ;; Check if there is a context at that line:
472 (mapcar (lambda (overlay) (when (overlay-get overlay 'project-buffer-occur-context)
473 (setq context (overlay-get overlay 'project-buffer-occur-context))))
476 (error "No occurrence on this line"))
477 (let ((file-name (car context))
478 (occurrence (nth 1 context))
479 (regexp (nth 2 context)))
481 (let ((occ-line-num (car occurrence))
482 (occ-line-str (nth 1 occurrence))
483 (occ-before-str (nth 2 occurrence))
484 (occ-after-str (nth 3 occurrence)))
485 (project-buffer-occur-goto-matching-string file-name occ-line-num occ-line-str occ-before-str occ-after-str regexp other-window)
486 (project-buffer-occur-highlight-current regexp (point) (+ (point) (length occ-line-str))))
487 (project-buffer-occur-goto-file file-name other-window)))))
490 (defun project-buffer-occur-delete-line()
491 "Delete the current occurrence line from this buffer."
493 (goto-char (point-at-bol))
494 (let ((start (point))
496 (inhibit-read-only t))
497 (if (looking-at "^[0-9]+ occurrence")
498 (progn (project-buffer-occur-next-file)
499 (setq end (if (eq start (point)) (point-max) (point)))
500 (delete-region start end))
501 (progn (forward-line 1)
505 (project-buffer-occur-previous-file)
506 (looking-at "^[0-9]+")
507 (let ((num (string-to-int (buffer-substring-no-properties (match-beginning 0) (match-end 0)))))
510 (progn (delete-region (match-beginning 0) (match-end 0))
511 (insert (propertize (format "%i" (1- num))
513 'mouse-face 'highlight
514 'face 'project-buffer-occur-file-line)))))))
515 (delete-region start end)))))
519 ;; Interactive commands:
523 (defun project-buffer-occur-mouse-find-file(event)
524 "Goto the selected occurrence."
526 (set-buffer (window-buffer (posn-window (event-end event))))
527 (project-buffer-occur-goto-occurrence-at-pos (posn-point (event-end event)) t))
530 (defun project-buffer-occur-goto-occurrence()
531 "Goto the selected occurrence."
533 (project-buffer-occur-goto-occurrence-at-pos (point) nil))
536 (defun project-buffer-occur-goto-occurrence-other-window()
537 "Goto the selected occurrence in another window."
539 (project-buffer-occur-goto-occurrence-at-pos (point) t))
542 (defun project-buffer-occur-view-occurrence()
543 "View the selected occurrence without leaving the project-buffer."
545 (let ((buffer (current-buffer)))
546 (project-buffer-occur-goto-occurrence-at-pos (point) t)
547 (let ((window (get-buffer-window buffer)))
549 (select-window window)))))
552 (defun project-buffer-occur-next-occurrence()
553 "Go to the next occurrence."
556 (goto-char (point-at-bol))
557 (while (and (not (eobp))
558 (looking-at "^[0-9]+ occurrence"))
562 (defun project-buffer-occur-previous-occurrence()
563 "Go to the next occurrence."
566 (goto-char (point-at-bol))
567 (while (and (not (bobp))
568 (looking-at "^[0-9]+ occurrence"))
571 (looking-at "^[0-9]+ occurrence"))
572 (project-buffer-occur-next)))
575 (defun project-buffer-occur-next-file()
576 "Go to the next file."
578 (let ((current (point)))
580 (goto-char (point-at-bol))
581 (while (and (not (eobp))
582 (not (looking-at "^[0-9]+ occurrence")))
584 (unless (looking-at "^[0-9]+ occurrence")
585 (goto-char current))))
588 (defun project-buffer-occur-previous-file()
589 "Go to the next file."
591 (let ((current (point)))
593 (goto-char (point-at-bol))
594 (while (and (not (eobp))
595 (not (looking-at "^[0-9]+ occurrence")))
597 (unless (looking-at "^[0-9]+ occurrence")
598 (goto-char current))))
601 (defun project-buffer-occur-view-next-occurrence()
602 "Go to the next occurrence and view it."
604 (project-buffer-occur-next-occurrence)
605 (project-buffer-occur-view-occurrence))
608 (defun project-buffer-occur-view-previous-occurrence()
609 "Go to the next occurrence."
611 (project-buffer-occur-previous-occurrence)
612 (project-buffer-occur-view-occurrence))
615 (defun project-buffer-occur-help ()
616 "Display help for `project-buffer-occur' mode."
618 (describe-function 'project-buffer-occur-mode))
621 (defun project-buffer-occur-rename-buffer()
622 "Rename the buffer; make its name uniq."
624 (let ((new-name (format "*Project-Buffer-Occur:%s*" project-buffer-occur-saved-project-buffer)))
625 (rename-buffer new-name t)))
628 (defun project-buffer-occur-refresh()
629 "Refresh the buffer."
631 (let ((inhibit-read-only t))
632 (project-buffer-occur-clear-overlays)
634 (let ((regexp (nth 0 project-buffer-occur-saved-regexp))
635 (all-files (nth 1 project-buffer-occur-saved-regexp))
636 (project (nth 2 project-buffer-occur-saved-regexp))
637 (occur-buffer (current-buffer)))
638 ;; Fill the occur buffer with all occurrences:
640 (set-buffer project-buffer-occur-saved-project-buffer)
642 (project-buffer-apply-to-each-file 'project-buffer-occur-research regexp occur-buffer)
643 (unless (project-buffer-apply-to-marked-files 'project-buffer-occur-research regexp occur-buffer)
644 (project-buffer-apply-to-project-files project 'project-buffer-occur-research regexp occur-buffer)))))))
653 (defun project-buffer-occur(regexp all-files)
654 "Search REGEXP in the project files; if ALL-FILES is t the
655 research will occur in all project's files; if ALL-FILES is
656 false, the research will occur in all marked files unless there
657 are none in which case it will occur in all files of the current
658 project (current project is determined by the cursor position)."
660 (list (project-buffer-read-regexp (format "List lines matching regexp%s: " (if current-prefix-arg " [all files]" "")))
662 (unless project-buffer-status (error "Not in project-buffer buffer"))
663 (unless (and regexp (not (string-equal regexp "")))
664 (error "Invalid regexp"))
665 ;; Generate an occur buffer:
666 (let ((pb-buffer (current-buffer)))
667 (let ((occur-buffer (project-buffer-occur-get-and-clear-occur-buffer))
668 (project-name (project-buffer-get-current-project-name))
669 (project-directory default-directory))
670 ;; Set the local variable:
671 (with-current-buffer occur-buffer
672 (cd project-directory)
673 (setq project-buffer-occur-saved-project-buffer pb-buffer)
674 (setq project-buffer-occur-saved-regexp (list regexp all-files project-name)))
675 ;; Fill the occur buffer with all occurrences:
677 (project-buffer-apply-to-each-file 'project-buffer-occur-research regexp occur-buffer)
678 (unless (project-buffer-apply-to-marked-files 'project-buffer-occur-research regexp occur-buffer)
679 (project-buffer-apply-to-project-files project-name
680 'project-buffer-occur-research regexp occur-buffer)))
681 (with-current-buffer occur-buffer
682 (goto-char (point-min)))
683 (display-buffer occur-buffer)
690 (provide 'project-buffer-occur)
692 ;;; project-buffer-occur.el ends here