8

Here's what I have:

(defun my-show-duplicate-lines ()
  (interactive)
  (highlight-lines-matching-regexp
   (concat "^"
           (regexp-quote
            (substring-no-properties
             (thing-at-point 'line) 0 -1))
           "$")
   font-lock-warning-face))

My intention is to add this to post-command-hook to find lines where I'm duplicating logic, but when I run the command a second time, it's ineffective (and the old font-locking is still in effect).

The second problem is caused by font-lock not refreshing itself. I've tried adding a (font-lock-mode -1) (font-lock-mode 1) to the definition, but it was ineffective.

I have no idea why the command would be only good for one run, though.

  • Try wrapping the highlight-lines-matching-regexp inside (let ((hi-lock-mode -1)) .. ). I did that to solve the same problem: github.com/kaushalmodi/.emacs.d/blob/… – Kaushal Modi Jun 11 '15 at 14:09
  • unhighlight-regexp can also be used. Regardless this feature is probably best implemented using a font-lock matching function that scans the buffer for duplicate lines and applies highlighting to them. This would handle unhighlighting automatically once there are no duplicate lines. – Jordon Biondo Jun 11 '15 at 14:11
  • @kaushalmodi no such luck :( thanks though – Sean Allred Jun 11 '15 at 14:12
  • @JordonBiondo I thought about that, but highlight-lines-matching-regexp must be applicable to this case -- it's almost a shoe-in. (Though I also thought about using overlays, but that's a concept I'm less familiar with.) – Sean Allred Jun 11 '15 at 14:13
  • You could copy the content of the buffer into another buffer, then run delete-duplicate-lines, then diff two buffers. – wvxvw Jun 11 '15 at 16:45
5
  1. Take a look at font-lock-keywords after you have called your function. You will see that it just has the regexp for the first line as the regexp to fontify. All you did was to pick up a given line and put a regexp to match it into font-lock-keywords - so only dups of that line get highlighted. IOW, the regexp for that first line is hard-coded in font-lock-keywords.

  2. Instead, you could use a FUNCTION in font-lock-keywords. But I would just search the buffer for dups of each line, in turn, and not bother with font-lock-keywords.

Here is one quick solution. It uses function hlt-highlight-region from the Highlight library (highlight.el), but you can use something else if you like.

(defun highlight-line-dups ()
  (interactive)
  (let ((count  0)
        line-re)
    (save-excursion
      (goto-char (point-min))
      (while (not (eobp))
        (setq count    0
              line-re  (concat "^" (regexp-quote (buffer-substring-no-properties
                                                  (line-beginning-position)
                                                  (line-end-position)))
                               "$"))
        (save-excursion
          (goto-char (point-min))
          (while (not (eobp))
            (if (not (re-search-forward line-re nil t))
                (goto-char (point-max))
              (setq count  (1+ count))
              (unless (< count 2)
                (hlt-highlight-region (line-beginning-position) (line-end-position)
                                      'font-lock-warning-face)
                (forward-line 1)))))
        (forward-line 1)))))

And here is a version that works on (a) the active region or (b) the full buffer if the region is not active:

(defun highlight-line-dups-region (&optional start end face msgp)
  (interactive `(,@(hlt-region-or-buffer-limits) nil t))
  (let ((count  0)
        line-re)
    (save-excursion
      (goto-char start)
      (while (< (point) end)
        (setq count    0
              line-re  (concat "^" (regexp-quote (buffer-substring-no-properties
                                                  (line-beginning-position)
                                                  (line-end-position)))
                               "$"))
        (save-excursion
          (goto-char start)
          (while (< (point) end)
            (if (not (re-search-forward line-re nil t))
                (goto-char end)
              (setq count  (1+ count))
              (unless (< count 2)
                (hlt-highlight-region
                 (line-beginning-position) (line-end-position)
                 face)
                (forward-line 1)))))
        (forward-line 1)))))

And if you want a different face for each set of dups then just bind a variable face in the let, and setq it to (hlt-next-face) next to where line-re is set, and replace font-lock-warning-face with face. Option hlt-auto-face-backgrounds controls the faces used.

(defun hlt-highlight-line-dups-region (&optional start end msgp)
  (interactive `(,@(hlt-region-or-buffer-limits) t))
  (let ((hlt-auto-faces-flag  t)
        count line line-re ignore-re)
    (save-excursion
      (goto-char start)
      (while (< (point) end)
        (setq count    0
              line     (buffer-substring-no-properties (line-beginning-position)
                                                       (line-end-position))
              ignore   (and (not (string= "" line))  "[ \t]*")
              line-re  (concat "^" ignore (regexp-quote line) ignore "$"))
        (save-excursion
          (goto-char start)
          (while (< (point) end)
            (if (not (re-search-forward line-re end t))
                (goto-char end)
              (setq count  (1+ count))
              (unless (< count 2)
                (hlt-highlight-region (line-beginning-position) (line-end-position))
                (forward-line 1)))))
        (forward-line 1)))))
  • I was actually just working on something that looks almost the exact same, function for function! One thing I would suggest is stripping the leading/trailing whitespace from the line's text and adding something like [ \t]* to the beginning and end of the regexp so lines at different indent levels still match. – Jordon Biondo Jun 11 '15 at 15:18
  • @JordonBiondo: But that's not what the OP asked for. Anything is possible, but I took my cue from the question and the solution attempt: He apparently really wants to match exactly the line text, and starting at bol, i.e., not disregarding indentation or trailing whitespace. But yes, lots of variants are possible. Dunno how useful anything like this really is. I guess it depends on what you want to do with it. – Drew Jun 11 '15 at 15:36
  • Well, my use case is to recognize where logic is being duplicated so I can try to optimize :) I'm sketching out an algorithm and using a formal syntax, so exact duplicates are more than possible. – Sean Allred Jun 11 '15 at 16:14
  • Not sure what you mean, Sean. But if you do want to ignore leading and trailing whitespace, as @JordonBiondo suggested, then just do as he suggested: add a possible-whitespace prefix and suffix to the regexp. – Drew Jun 11 '15 at 17:06
  • I tried using your last function but when compiling the function definition I get setq: Symbol's value as variable is void: hlt-highlight-line-dups-ignore-regexp. How is this variable defined? – Patrick Jun 20 '15 at 9:51
1

How about using overlay instead of font-lock?

;; https://github.com/ShingoFukuyama/ov.el
(require 'ov)

(defun my-highlight-duplicate-lines-in-region ()
  (interactive)
  (if mark-active
      (let* (($beg (region-beginning))
             ($end (region-end))
             ($st (buffer-substring-no-properties
                   $beg $end))
             ($lines)
             $dup)
        (deactivate-mark t)
        (save-excursion
          (goto-char $beg)
          (while (< (point) $end)
            (let* (($b (point))
                   ($e (point-at-eol))
                   ($c (buffer-substring-no-properties $b $e))
                   ($a (assoc $c $lines)))
              (when (not (eq $b $e))
                (if $a
                    (progn
                      (setq $dup (cons $b $dup))
                      (setq $dup (cons (cdr $a) $dup)))
                  (setq $lines
                        (cons (cons $c $b) $lines)))))
            (forward-line 1))
          (mapc (lambda ($p)
                  (ov-set (ov-line $p) 'face '(:foreground "red")))
                (sort (delete-dups $dup) '<))))))

Make region, and then M-x my-highlight-duplicate-lines-in-region You can clear all overlays by M-x ov-clear

0

This is a little sketchy, but with some effort (see C-h fediff-buffersRET for the info on the HOOK argument) you could make it display better / perform a better cleanup upon exiting the diff mode:

(defun my/show-duplicate-lines (beg end)
  (interactive "r")
  (unless (region-active-p)
    (setf beg (point-min)
          end (point-max)))
  (let ((copy (buffer-substring beg end))
        (original (current-buffer))
        (dupes-buffer (get-buffer-create (format "%s[dupes]" (buffer-name)))))
    (with-current-buffer dupes-buffer
      (erase-buffer)
      (insert copy)
      (delete-duplicate-lines (point-min) (point-max))
      (ediff-buffers original dupes-buffer))))
0

Improvement on answer above by Shingo Fukuyama.

This version checks for duplicate lines in active region but if there is none, searches the whole buffer.

(require 'ov)
(defun highlight-duplicate-lines-in-region-or-buffer ()
(interactive)

  (let* (
    ($beg (if mark-active (region-beginning) (point-min)))
    ($end (if mark-active (region-end) (point-max)))
    ($st (buffer-substring-no-properties $beg $end))
    ($lines)
    ($dup))
  (deactivate-mark t)
  (save-excursion
    (goto-char $beg)
    (while (< (point) $end)
      (let* (($b (point))
         ($e (point-at-eol))
         ($c (buffer-substring-no-properties $b $e))
         ($a (assoc $c $lines)))
    (when (not (eq $b $e))
      (if $a
          (progn
        (setq $dup (cons $b $dup))
        (setq $dup (cons (cdr $a) $dup)))
        (setq $lines
          (cons (cons $c $b) $lines)))))
      (forward-line 1))
    (mapc (lambda ($p)
        (ov-set (ov-line $p) 'face '(:foreground "red")))
      (sort (delete-dups $dup) '<)))))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.