Make occur handle multi-line matches cleanly with context.

http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01280.html

* replace.el (occur-accumulate-lines): Add optional arg `pt'.
(occur-engine): Add local variables `ret', `prev-after-lines',
`prev-lines'.  Use more arguments for `occur-context-lines'.
Set first elem of its returned list to `data', and the second elem
to `prev-after-lines'.  Don't print the separator line.
In the end, print remaining context after-lines.
(occur-context-lines): Add new arguments `begpt', `endpt',
`lines', `prev-lines', `prev-after-lines'.  Rewrite to combine
after-lines of the previous match with before-lines of the
current match and not overlap them.  Return a list with two
values: the output line and the list of context after-lines.

* search.texi (Other Repeating Search): Remove line that `occur'
can not handle multiline matches.

* occur-testsuite.el (occur-tests): Add tests for context lines.
This commit is contained in:
Juri Linkov 2010-03-30 19:03:08 +03:00
parent 47c88c067f
commit dc2d2590b2
7 changed files with 308 additions and 22 deletions

View file

@ -1,3 +1,8 @@
2010-03-30 Juri Linkov <juri@jurta.org>
* search.texi (Other Repeating Search): Remove line that `occur'
can not handle multiline matches.
2010-03-30 Eli Zaretskii <eliz@gnu.org>
* mule.texi (International): Mention support of bidirectional editing.

View file

@ -1311,8 +1311,7 @@ Prompt for a regexp, and display a list showing each line in the
buffer that contains a match for it. To limit the search to part of
the buffer, narrow to that part (@pxref{Narrowing}). A numeric
argument @var{n} specifies that @var{n} lines of context are to be
displayed before and after each matching line. Currently,
@code{occur} can not correctly handle multiline matches.
displayed before and after each matching line.
@kindex RET @r{(Occur mode)}
@kindex o @r{(Occur mode)}

View file

@ -128,8 +128,6 @@ for users to customize.
** Enhance scroll-bar to handle tall line (similar to line-move).
** Make occur handle multi-line matches cleanly with context.
** In Custom buffers, put the option that turns a mode on or off first,
using a heuristic of some kind?

View file

@ -1,3 +1,20 @@
2010-03-30 Juri Linkov <juri@jurta.org>
Make occur handle multi-line matches cleanly with context.
http://lists.gnu.org/archive/html/emacs-devel/2010-03/msg01280.html
* replace.el (occur-accumulate-lines): Add optional arg `pt'.
(occur-engine): Add local variables `ret', `prev-after-lines',
`prev-lines'. Use more arguments for `occur-context-lines'.
Set first elem of its returned list to `data', and the second elem
to `prev-after-lines'. Don't print the separator line.
In the end, print remaining context after-lines.
(occur-context-lines): Add new arguments `begpt', `endpt',
`lines', `prev-lines', `prev-after-lines'. Rewrite to combine
after-lines of the previous match with before-lines of the
current match and not overlap them. Return a list with two
values: the output line and the list of context after-lines.
2010-03-30 Juri Linkov <juri@jurta.org>
* replace.el (occur-accumulate-lines): Fix a bug where the first

View file

@ -1005,8 +1005,10 @@ which means to discard all text properties."
:group 'matching
:version "22.1")
(defun occur-accumulate-lines (count &optional keep-props)
(defun occur-accumulate-lines (count &optional keep-props pt)
(save-excursion
(when pt
(goto-char pt))
(let ((forwardp (> count 0))
result beg end moved)
(while (not (or (zerop count)
@ -1189,12 +1191,15 @@ See also `multi-occur'."
(when (buffer-live-p buf)
(let ((matches 0) ;; count of matched lines
(lines 1) ;; line count
(prev-after-lines nil) ;; context lines of prev match
(prev-lines nil) ;; line number of prev match endpt
(matchbeg 0)
(origpt nil)
(begpt nil)
(endpt nil)
(marker nil)
(curstring "")
(ret nil)
(inhibit-field-text-motion t)
(headerpt (with-current-buffer out-buf (point))))
(with-current-buffer buf
@ -1271,14 +1276,17 @@ See also `multi-occur'."
;; The simple display style
out-line
;; The complex multi-line display style.
(occur-context-lines out-line nlines keep-props)
)))
(setq ret (occur-context-lines
out-line nlines keep-props begpt endpt
lines prev-lines prev-after-lines))
;; Set first elem of the returned list to `data',
;; and the second elem to `prev-after-lines'.
(setq prev-after-lines (nth 1 ret))
(nth 0 ret))))
;; Actually insert the match display data
(with-current-buffer out-buf
(let ((beg (point))
(end (progn (insert data) (point))))
(unless (= nlines 0)
(insert "-------\n")))))
(end (progn (insert data) (point)))))))
(goto-char endpt))
(if endpt
(progn
@ -1289,7 +1297,13 @@ See also `multi-occur'."
(if (and (bolp) (eolp)) 1 0)))
;; On to the next match...
(forward-line 1))
(goto-char (point-max))))))
(goto-char (point-max)))
(setq prev-lines (1- lines)))
;; Flush remaining context after-lines.
(when prev-after-lines
(with-current-buffer out-buf
(insert (apply #'concat (occur-engine-add-prefix
prev-after-lines)))))))
(when (not (zerop matches)) ;; is the count zero?
(setq globalcount (+ globalcount matches))
(with-current-buffer out-buf
@ -1345,18 +1359,60 @@ See also `multi-occur'."
;; Generate context display for occur.
;; OUT-LINE is the line where the match is.
;; NLINES and KEEP-PROPS are args to occur-engine.
;; LINES is line count of the current match,
;; PREV-LINES is line count of the previous match,
;; PREV-AFTER-LINES is a list of after-context lines of the previous match.
;; Generate a list of lines, add prefixes to all but OUT-LINE,
;; then concatenate them all together.
(defun occur-context-lines (out-line nlines keep-props)
(apply #'concat
(nconc
(occur-engine-add-prefix
(nreverse (cdr (occur-accumulate-lines
(- (1+ (abs nlines))) keep-props))))
(list out-line)
(if (> nlines 0)
(occur-engine-add-prefix
(cdr (occur-accumulate-lines (1+ nlines) keep-props)))))))
(defun occur-context-lines (out-line nlines keep-props begpt endpt
lines prev-lines prev-after-lines)
;; Find after- and before-context lines of the current match.
(let ((before-lines
(nreverse (cdr (occur-accumulate-lines
(- (1+ (abs nlines))) keep-props begpt))))
(after-lines
(cdr (occur-accumulate-lines
(1+ nlines) keep-props endpt)))
separator)
;; Combine after-lines of the previous match
;; with before-lines of the current match.
(when prev-after-lines
;; Don't overlap prev after-lines with current before-lines.
(if (>= (+ prev-lines (length prev-after-lines))
(- lines (length before-lines)))
(setq prev-after-lines
(butlast prev-after-lines
(- (length prev-after-lines)
(- lines prev-lines (length before-lines) 1))))
;; Separate non-overlapping context lines with a dashed line.
(setq separator "-------\n")))
(when prev-lines
;; Don't overlap current before-lines with previous match line.
(if (<= (- lines (length before-lines))
prev-lines)
(setq before-lines
(nthcdr (- (length before-lines)
(- lines prev-lines 1))
before-lines))
;; Separate non-overlapping before-context lines.
(unless (> nlines 0)
(setq separator "-------\n"))))
(list
;; Return a list where the first element is the output line.
(apply #'concat
(append
(and prev-after-lines
(occur-engine-add-prefix prev-after-lines))
(and separator (list separator))
(occur-engine-add-prefix before-lines)
(list out-line)))
;; And the second element is the list of context after-lines.
(if (> nlines 0) after-lines))))
;; It would be nice to use \\[...], but there is no reasonable way
;; to make that display both SPC and Y.

View file

@ -1,3 +1,7 @@
2010-03-30 Juri Linkov <juri@jurta.org>
* occur-testsuite.el (occur-tests): Add tests for context lines.
2010-03-23 Juri Linkov <juri@jurta.org>
* occur-testsuite.el: New file.

View file

@ -107,7 +107,214 @@ fx
:fx
:
")
)
;; * Test non-overlapping context lines with matches at bob/eob.
("x" 1 "\
ax
b
c
d
ex
f
g
hx
" "\
3 matches for \"x\" in buffer: *temp*
1:ax
:b
-------
:d
5:ex
:f
-------
:g
8:hx
")
;; * Test non-overlapping context lines with matches not at bob/eob.
("x" 1 "\
a
bx
c
d
ex
f
" "\
2 matches for \"x\" in buffer: *temp*
:a
2:bx
:c
-------
:d
5:ex
:f
")
;; * Test overlapping context lines with matches at bob/eob.
("x" 2 "\
ax
bx
c
dx
e
f
gx
h
i
j
kx
" "\
5 matches for \"x\" in buffer: *temp*
1:ax
2:bx
:c
4:dx
:e
:f
7:gx
:h
:i
:j
11:kx
")
;; * Test overlapping context lines with matches not at bob/eob.
("x" 2 "\
a
b
cx
d
e
f
gx
h
i
" "\
2 matches for \"x\" in buffer: *temp*
:a
:b
3:cx
:d
:e
:f
7:gx
:h
:i
")
;; * Test overlapping context lines with empty first and last line..
("x" 2 "\
b
cx
d
e
f
gx
h
" "\
2 matches for \"x\" in buffer: *temp*
:
:b
3:cx
:d
:e
:f
7:gx
:h
:
")
;; * Test multi-line overlapping context lines.
("x\n.x" 2 "\
ax
bx
c
d
ex
fx
g
h
i
jx
kx
" "\
3 matches for \"x^J.x\" in buffer: *temp*
1:ax
:bx
:c
:d
5:ex
:fx
:g
:h
:i
10:jx
:kx
")
;; * Test multi-line non-overlapping context lines.
("x\n.x" 2 "\
ax
bx
c
d
e
f
gx
hx
" "\
2 matches for \"x^J.x\" in buffer: *temp*
1:ax
:bx
:c
:d
-------
:e
:f
7:gx
:hx
")
;; * Test non-overlapping negative (before-context) lines.
("x" -2 "\
a
bx
c
d
e
fx
g
h
ix
" "\
3 matches for \"x\" in buffer: *temp*
:a
2:bx
-------
:d
:e
6:fx
-------
:g
:h
9:ix
")
;; * Test overlapping negative (before-context) lines.
("x" -3 "\
a
bx
c
dx
e
f
gx
h
" "\
3 matches for \"x\" in buffer: *temp*
:a
2:bx
:c
4:dx
:e
:f
7:gx
")
)
"List of tests for `occur'.
Each element has the format:
\(REGEXP NLINES INPUT-BUFFER-STRING OUTPUT-BUFFER-STRING).")