changeset 107653:bfde3c2dbef5

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.
author Juri Linkov <juri@jurta.org>
date Tue, 30 Mar 2010 19:03:08 +0300
parents 861199fb7574
children 9a8d281f69fd
files doc/emacs/ChangeLog doc/emacs/search.texi etc/TODO lisp/ChangeLog lisp/replace.el test/ChangeLog test/occur-testsuite.el
diffstat 7 files changed, 308 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/doc/emacs/ChangeLog	Tue Mar 30 18:44:50 2010 +0300
+++ b/doc/emacs/ChangeLog	Tue Mar 30 19:03:08 2010 +0300
@@ -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.
--- a/doc/emacs/search.texi	Tue Mar 30 18:44:50 2010 +0300
+++ b/doc/emacs/search.texi	Tue Mar 30 19:03:08 2010 +0300
@@ -1311,8 +1311,7 @@
 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)}
--- a/etc/TODO	Tue Mar 30 18:44:50 2010 +0300
+++ b/etc/TODO	Tue Mar 30 19:03:08 2010 +0300
@@ -128,8 +128,6 @@
 
 ** 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?
 
--- a/lisp/ChangeLog	Tue Mar 30 18:44:50 2010 +0300
+++ b/lisp/ChangeLog	Tue Mar 30 19:03:08 2010 +0300
@@ -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
--- a/lisp/replace.el	Tue Mar 30 18:44:50 2010 +0300
+++ b/lisp/replace.el	Tue Mar 30 19:03:08 2010 +0300
@@ -1005,8 +1005,10 @@
   :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 @@
 	(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 @@
 				;; 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 @@
 				       (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 @@
 ;; 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.
--- a/test/ChangeLog	Tue Mar 30 18:44:50 2010 +0300
+++ b/test/ChangeLog	Tue Mar 30 19:03:08 2010 +0300
@@ -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.
--- a/test/occur-testsuite.el	Tue Mar 30 18:44:50 2010 +0300
+++ b/test/occur-testsuite.el	Tue Mar 30 19:03:08 2010 +0300
@@ -107,7 +107,214 @@
        :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).")