]> code.delx.au - gnu-emacs-elpa/blob - loccur.el
add5a44f0cff1e9326bbf8dc70b0b0315cac87e8
[gnu-emacs-elpa] / loccur.el
1 ;;; loccur.el --- Perform an occur-like folding in current buffer
2
3 ;; Copyright (C) 2009-2015 Alexey Veretennikov
4 ;;
5 ;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
6 ;; Created: 2009-09-08
7 ;; Version: 1.2.2
8 ;; Keywords: matching
9 ;; URL: https://github.com/fourier/loccur
10 ;; Compatibility: GNU Emacs 23.x, GNU Emacs 24.x
11 ;;
12 ;; This file is NOT part of GNU Emacs.
13 ;;
14 ;; This program is free software; you can redistribute it and/or
15 ;; modify it under the terms of the GNU General Public License
16 ;; as published by the Free Software Foundation; either version 2
17 ;; of the License, or (at your option) any later version.
18 ;;
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
23 ;;
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
26 ;;
27 ;;; Commentary:
28 ;;
29 ;; Add the following to your .emacs file:
30 ;;
31 ;; (require 'loccur)
32 ;; ;; defines shortcut for loccur of the current word
33 ;; (define-key global-map [(control o)] 'loccur-current)
34 ;; ;; defines shortcut for the interactive loccur command
35 ;; (define-key global-map [(control meta o)] 'loccur)
36 ;; ;; defines shortcut for the loccur of the previously found word
37 ;; (define-key global-map [(control shift o)] 'loccur-previous-match)
38 ;;
39 ;;; Issues:
40 ;; Using with smooth-scrolling.el sometimes
41 ;; gives unexpected jumps in loccur mode
42 ;;
43 ;;; TODO:
44 ;;
45 ;;; Change Log:
46 ;;
47 ;; 2015-12-27 (1.2.2)
48 ;; + Preparation for GNU ELPA submission. Removed contributions
49 ;; without signed papers
50 ;; + added loccur-face - face to highlight text, by default isearch
51 ;;
52 ;; 2013-10-22 (1.2.1)
53 ;; + Added custom option loccur-jump-beginning-of-line; removed some
54 ;; of cl dependencies
55 ;;
56 ;; 2010-03-07 (1.1.1)
57 ;; + Default value is taken from prompt instead of an edit area
58 ;; (thanks to Nathaniel Flath)
59 ;;
60 ;; 2009-10-05 (1.1.0)
61 ;; + Added highlighting of the matched strings
62 ;; + Now inserts selected region to the prompt
63 ;; + Added defun for applying last found regexp(loccur-previous-match)
64 ;; + Added intangible property together with invisibility
65 ;;
66 ;; 2009-09-08 (1.0.0)
67 ;; Initial Release.
68 ;;
69 ;;; Code:
70
71 (require 'cl-lib)
72
73 (defgroup loccur nil
74 "Perform an occur-like folding in current buffer."
75 :group 'tools)
76
77 ;; should be defined before define-minor-mode
78 (defvar loccur-mode-map
79 (let ((map (make-sparse-keymap)))
80 (define-key map (kbd "RET") '(lambda () (interactive) (loccur nil)))
81 ;; redefine Ctrl+Up/Down to Up/Down, since it looks like some problem
82 ;; with backward-paragraph and forward-paragraph with invisible overlays
83 (define-key map (kbd "<C-up>") 'previous-line)
84 (define-key map (kbd "<C-down>") 'next-line)
85 map)
86 "Keymap for the variable `loccur-mode'.")
87
88 ;;;###autoload
89 (define-minor-mode loccur-mode
90 "Minor mode for navigating through the file.
91 Hides all lines without matches like `occur' does, but without opening
92 a new window."
93 :lighter " loccur"
94 :keymap loccur-mode-map
95 (if loccur-mode
96 (loccur-1 loccur-current-search)
97 (loccur-remove-overlays)
98 (recenter)))
99
100 (defface loccur-face
101 '((t (:inherit isearch)))
102 "Loccur face"
103 :group 'loccur)
104
105
106 (defconst loccur-overlay-invisible-property-name 'loccur-invisible-overlay
107 "Property name of the overlay for all invisible text.")
108
109 (defconst loccur-overlay-visible-property-name 'loccur-visible-overlay
110 "Property name of the overlay for all visible text.")
111
112 (defcustom loccur-jump-beginning-of-line nil
113 "Set cursor to the beginning of the line when the loccur function is called.
114 Default: nil"
115 :type '(boolean)
116 :group 'loccur)
117
118 (defcustom loccur-highlight-matching-regexp t
119 "If set to nil, do not highlight matching words.
120 Default: t"
121 :type '(boolean)
122 :group 'loccur)
123
124 (defvar loccur-history nil
125 "History of previously searched expressions for the prompt.")
126
127 (defvar-local loccur-last-match nil
128 "Last match found.")
129
130 (defvar-local loccur-overlay-list nil
131 "A list of currently active overlays.")
132
133 (defvar-local loccur-current-search nil
134 "The expression to search in the current active mode.")
135
136 (defun loccur-current ()
137 "Call `loccur' for the current word."
138 (interactive)
139 (loccur (current-word)))
140
141
142 (defun loccur-previous-match ()
143 "Call `loccur' for the previously found word."
144 (interactive)
145 (loccur loccur-last-match))
146
147 (defun loccur-no-highlight (regex)
148 "Perform search like loccur, but temporary removing match highlight.
149 REGEX is regexp to search"
150 (interactive
151 (if loccur-mode
152 nil
153 (list (read-string "Loccur: " (loccur-prompt) 'loccur-history))))
154 (let ((loccur-highlight-matching-regexp nil))
155 (loccur regex)))
156
157 (defun loccur-toggle-highlight (&optional arg)
158 "Toggle the highlighting of the match.
159 Optional argument ARG if t turn highlight on, off otherwise."
160 (interactive)
161 (setq loccur-highlight-matching-regexp (not loccur-highlight-matching-regexp))
162 (when loccur-mode
163 (dolist (ovl loccur-overlay-list)
164 (when (overlay-get ovl loccur-overlay-visible-property-name)
165 (overlay-put ovl 'face (if loccur-highlight-matching-regexp 'loccur-face nil))))))
166
167 (defun loccur (regex)
168 "Perform a simple grep in current buffer.
169
170 This command hides all lines from the current buffer except those
171 containing the regular expression REGEX. A second call of the function
172 unhides lines again"
173 (interactive
174 (if loccur-mode
175 (list nil)
176 (list (read-string "Loccur: " (loccur-prompt) 'loccur-history))))
177 (if (or loccur-mode
178 (= (length regex) 0))
179 (progn
180 ;; remove current search and turn off loccur mode
181 ;; to allow to call `loccur' multiple times
182 (setf loccur-current-search nil)
183 (loccur-mode 0))
184 ;; otherwise do as usual
185 ;; if the regex argument is not equal to previous search
186 (when (not (string-equal regex loccur-current-search))
187 (cl-pushnew regex loccur-history)
188 (setf loccur-current-search regex)
189 (loccur-mode)
190 (when loccur-jump-beginning-of-line
191 (beginning-of-line))))) ; optionally jump to the beginning of line
192
193
194 (defun loccur-prompt ()
195 "Return the default value of the prompt.
196
197 Default value for prompt is a current word or active region(selection),
198 if its size is 1 line"
199 (let ((prompt
200 (if (and transient-mark-mode
201 mark-active)
202 (let ((pos1 (region-beginning))
203 (pos2 (region-end)))
204 ;; Check if the start and the of an active region is on
205 ;; the same line
206 (if (= (line-number-at-pos pos1)
207 (line-number-at-pos pos2))
208 (buffer-substring-no-properties pos1 pos2)))
209 (current-word))))
210 prompt))
211
212
213 (defun loccur-1 (regex)
214 "Implementation of the `loccur' functionality.
215
216 REGEX is an argument to `loccur'."
217 (let* ((buffer-matches (loccur-find-matches regex))
218 (ovl-bounds (loccur-create-overlay-bounds-btw-lines buffer-matches)))
219 (setq loccur-overlay-list
220 (loccur-create-invisible-overlays ovl-bounds))
221
222 (setq loccur-overlay-list
223 (append loccur-overlay-list
224 (loccur-create-highlighted-overlays buffer-matches)))
225 (setq loccur-last-match regex)
226 (recenter)))
227
228 (defun loccur-create-highlighted-overlays (buffer-matches)
229 "Create the list of overlays for BUFFER-MATCHES."
230 (let ((overlays
231 (mapcar (lambda (match)
232 (make-overlay
233 (nth 1 match)
234 (nth 2 match)
235 (current-buffer) t nil))
236 buffer-matches)))
237 (mapc (lambda (ovl)
238 (overlay-put ovl loccur-overlay-visible-property-name t)
239 (when loccur-highlight-matching-regexp
240 (overlay-put ovl 'face 'loccur-face)))
241 overlays)))
242
243
244 (defun loccur-create-invisible-overlays (ovl-bounds)
245 "Create a list of invisible overlays by given OVL-BOUNDS."
246 (let ((overlays
247 (mapcar (lambda (bnd)
248 (make-overlay
249 (car bnd)
250 (cadr bnd)
251 (current-buffer) t nil))
252 ovl-bounds)))
253 (mapc (lambda (ovl)
254 (overlay-put ovl loccur-overlay-invisible-property-name t)
255 (overlay-put ovl 'invisible t)
256 ;; force intangible property if invisible property
257 ;; does not automatically set it
258 (overlay-put ovl 'intangible t))
259 overlays)))
260
261
262 (defun loccur-remove-overlays ()
263 "Remove all overlays."
264 (remove-overlays (point-min) (point-max) loccur-overlay-visible-property-name t)
265 (remove-overlays (point-min) (point-max) loccur-overlay-invisible-property-name t)
266 (setq loccur-overlay-list nil))
267
268
269 (defun loccur-create-overlay-bounds-btw-lines (buffer-matches)
270 "Create a list of overlays between matched lines BUFFER-MATCHES."
271 (let ((prev-end (point-min))
272 (overlays (list)))
273 (when buffer-matches
274 (mapcar (lambda (line)
275 (let ((beginning (car line)))
276 (unless ( = (- beginning prev-end) 1)
277 (let ((ovl-end (1- beginning)))
278 (push (list prev-end ovl-end) overlays)))
279 (setq prev-end (nth 3 line))))
280 buffer-matches)
281 (push (list (1+ prev-end) (point-max)) overlays)
282 (setq overlays (nreverse overlays)))))
283
284
285 (defun loccur-find-matches (regex)
286 "Find all occurences in the current buffer for given REGEX.
287
288 Returns a list of 4-number tuples, specifying begnning of the line,
289 1st match begin of a line, 1st match end of a line, end of a line
290 containing match"
291 (save-excursion
292 ;; Go to the beginnig of buffer
293 (goto-char (point-min))
294 ;; Set initial values for variables
295 (let ((matches 0)
296 (curpoint nil)
297 (endpoint nil)
298 (lines (list)))
299 ;; Search loop
300 (while (not (eobp))
301 (setq curpoint (point))
302 ;; if something found
303 (when (setq endpoint (re-search-forward regex nil t))
304 (save-excursion
305 (let ((found-begin (match-beginning 0))
306 (found-end (match-end 0)))
307 ;; Get the start and the and of the matching line
308 ;; and store it to the overlays array
309 (goto-char found-begin)
310 (setq endpoint (line-end-position))
311 (push (list (line-beginning-position)
312 found-begin
313 found-end
314 endpoint) lines)))
315 ;; maybe add some code to highlight matches like in occur-mode?
316 ;; goto the end of line for any case
317 (goto-char endpoint))
318 (forward-line 1))
319 (setq lines (nreverse lines)))))
320
321
322
323
324
325 (provide 'loccur)
326 ;;; loccur.el ends here