Subject: bug#6871: Please make linum-mode per buffer, not per major mode

From: Stefan Monnier <[email-masked]>
Date: Thu, 19 Aug 2010 16:45:16 +0200

> I think I have attached the diff now ;-)

Yes, this time it looks right, thanks.

>>> There are some other changes to linum.el too.
>> Why? If they are not related, that should be sent as another patch.
> Maybe. Linum is rather small and it is quicker to do it this way.

I don't mind a few cosmetic changes as long as they don't drown the main
change, but in this case, the changes aren't cosmetic at all.
They don't sound bad, tho.  So please post them separately, with an
explanation for why they're right.

And please resend a new patch that only fixes the permanent-local part
of linum-mode, because I'm having a hard time figuring out which part
belongs to what.

        Stefan

> === modified file 'lisp/linum.el'
> --- trunk/lisp/linum.el	2010-01-13 08:35:10 +0000
> +++ patched/lisp/linum.el	2010-08-19 04:22:04 +0000
> @@ -38,6 +38,8 @@
>    "Functions run in each buffer before line numbering starts.")
 
>  (mapc #'make-variable-buffer-local '(linum-overlays linum-available))
> +(put 'linum-overlays  'permanent-local t)
> +(put 'linum-available 'permanent-local t)
 
>  (defgroup linum nil
>    "Show line numbers in the left margin."
> @@ -58,13 +60,6 @@
>    "Face for displaying line numbers in the display margin."
>    :group 'linum)
 
> -(defcustom linum-eager t
> -  "Whether line numbers should be updated after each command.
> -The conservative setting `nil' might miss some buffer changes,
> -and you have to scroll or press \\[recenter-top-bottom] to update the numbers."
> -  :group 'linum
> -  :type 'boolean)
> -
>  (defcustom linum-delay nil
>    "Delay updates to give Emacs a chance for other changes."
>    :group 'linum
> @@ -76,30 +71,29 @@
>    :lighter ""                           ; for desktop.el
>    (if linum-mode
>        (progn
> -        (if linum-eager
>              (add-hook 'post-command-hook (if linum-delay
>                                               'linum-schedule
> -                                           'linum-update-current) nil t)
> -          (add-hook 'after-change-functions 'linum-after-change nil t))
> +                                       'linum-update-current-buffer) nil t)
> +        (add-hook 'before-change-functions 'linum-before-change nil t)
> +        (add-hook 'after-change-functions 'linum-after-change nil t)
>          (add-hook 'window-scroll-functions 'linum-after-scroll nil t)
>          ;; Using both window-size-change-functions and
>          ;; window-configuration-change-hook seems redundant. --Stef
>          ;; (add-hook 'window-size-change-functions 'linum-after-size nil t)
> -        (add-hook 'change-major-mode-hook 'linum-delete-overlays nil t)
>          (add-hook 'window-configuration-change-hook
> -                  ;; FIXME: If the buffer is shown in N windows, this
> -                  ;; will be called N times rather than once.  We should use
> -                  ;; something like linum-update-window instead.
> -                  'linum-update-current nil t)
> -        (linum-update-current))
> -    (remove-hook 'post-command-hook 'linum-update-current t)
> +                  ;; Update just the selected window
> +                  'linum-update-selected-window nil t)
> +        (setq linum-change-beg 1)
> +        (linum-update-current-buffer))
> +    (remove-hook 'post-command-hook 'linum-update-current-buffer t)
>      (remove-hook 'post-command-hook 'linum-schedule t)
>      ;; (remove-hook 'window-size-change-functions 'linum-after-size t)
>      (remove-hook 'window-scroll-functions 'linum-after-scroll t)
> +    (remove-hook 'before-change-functions 'linum-before-change t)
>      (remove-hook 'after-change-functions 'linum-after-change t)
> -    (remove-hook 'window-configuration-change-hook 'linum-update-current t)
> -    (remove-hook 'change-major-mode-hook 'linum-delete-overlays t)
> +    (remove-hook 'window-configuration-change-hook 'linum-update-selected-window t)
>      (linum-delete-overlays)))
> +(put 'linum-mode      'permanent-local t)
 
>  ;;;###autoload
>  (define-globalized-minor-mode global-linum-mode linum-mode linum-on)
> @@ -115,22 +109,34 @@
>    (dolist (w (get-buffer-window-list (current-buffer) nil t))
>      (set-window-margins w 0 (cdr (window-margins w)))))
 
> -(defun linum-update-current ()
> +(defun linum-update-current-buffer ()
>    "Update line numbers for the current buffer."
>    (linum-update (current-buffer)))
> +(put 'linum-update-current-buffer 'permanent-local-hook t)
 
>  (defun linum-update (buffer)
>    "Update line numbers for all windows displaying BUFFER."
>    (with-current-buffer buffer
> -    (when linum-mode
> +    (when (and linum-mode
> +               linum-change-beg)
>        (setq linum-available linum-overlays)
>        (setq linum-overlays nil)
>        (save-excursion
>          (mapc #'linum-update-window
>                (get-buffer-window-list buffer nil 'visible)))
>        (mapc #'delete-overlay linum-available)
> +      (setq linum-change-beg nil)
>        (setq linum-available nil))))
 
> +(defun linum-update-selected-window ()
> +  "Update line numbers for the selected window."
> +  (let ((here (window-point))
> +        ;; We might have scrolled or changed win config
> +        (linum-change-beg 1))
> +    (linum-update-window (selected-window))
> +    (goto-char here)))
> +(put 'linum-update-selected-window 'permanent-local-hook t)
> +
>  (defun linum-update-window (win)
>    "Update line numbers for the portion visible in window WIN."
>    (goto-char (window-start win))
> @@ -142,6 +148,7 @@
>                                        (count-lines (point-min) (point-max))))))
>                        (concat "%" (number-to-string w) "d")))))
>          (width 0))
> +    (when (<= linum-change-beg limit)
>      (run-hooks 'linum-before-numbering-hook)
>      ;; Create an overlay (or reuse an existing one) for each
>      ;; line visible in this window, if necessary.
> @@ -171,24 +178,40 @@
>        (let ((inhibit-point-motion-hooks t))
>          (forward-line))
>        (setq line (1+ line)))
> -    (set-window-margins win width (cdr (window-margins win)))))
> +      (set-window-margins win width (cdr (window-margins win))))))
> +
> +(defvar linum-change-beg nil
> +  "Position of change beginning, recorded after change.")
> +(make-variable-buffer-local 'linum-change-beg)
> +(put linum-change-beg 'permanent-local t)
> +
> +(defvar linum-changed-region-has-newline nil)
> +(defun linum-before-change (beg end)
> +  ;; Record new lines in changed region for check in after change function.
> +  (when (string-match-p "\n" (buffer-substring-no-properties beg end))
> +    (setq linum-changed-region-has-newline t)))
> +(put 'linum-before-change 'permanent-local-hook t)
 
>  (defun linum-after-change (beg end len)
> -  ;; update overlays on deletions, and after newlines are inserted
> -  (when (or (= beg end)
> -            (= end (point-max))
> +  ;; update overlays after newlines are delete or inserted
> +  (when (or linum-changed-region-has-newline
>              (string-match-p "\n" (buffer-substring-no-properties beg end)))
> -    (linum-update-current)))
> +    (setq linum-changed-region-has-newline nil)
> +    (setq linum-change-beg beg)))
> +(put 'linum-after-change 'permanent-local-hook t)
 
>  (defun linum-after-scroll (win start)
> -  (linum-update (window-buffer win)))
> +  (with-selected-window win
> +    (linum-update-selected-window)))
> +(put 'linum-after-scroll 'permanent-local-hook t)
 
>  ;; (defun linum-after-size (frame)
>  ;;   (linum-after-config))
 
>  (defun linum-schedule ()
>    ;; schedule an update; the delay gives Emacs a chance for display changes
> -  (run-with-idle-timer 0 nil #'linum-update-current))
> +  (run-with-idle-timer 0 nil #'linum-update-current-buffer))
> +(put 'linum-schedule 'permanent-local-hook t)
 
>  ;; (defun linum-after-config ()
>  ;;   (walk-windows (lambda (w) (linum-update (window-buffer w))) nil 'visible))