1 ;;; nlinum.el --- Show line numbers in the margin -*- lexical-binding: t -*-
3 ;; Copyright (C) 2012, 2014, 2015 Free Software Foundation, Inc.
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Keywords: convenience
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
24 ;; This is like linum-mode, but uses jit-lock to be (hopefully)
30 ;; - New custom variable `nlinum-format'.
31 ;; - Change in calling convention of `nlinum-format-function'.
34 ;; - New global mode `global-nlinum-mode'.
35 ;; - New config var `nlinum-format-function'.
39 (require 'linum) ;For its face.
41 (defvar nlinum--width 2)
42 (make-variable-buffer-local 'nlinum--width)
44 ;; (defvar nlinum--desc "")
47 (define-minor-mode nlinum-mode
48 "Toggle display of line numbers in the left margin (Linum mode).
49 With a prefix argument ARG, enable Linum mode if ARG is positive,
50 and disable it otherwise. If called from Lisp, enable the mode
51 if ARG is omitted or nil.
53 Linum mode is a buffer-local minor mode."
54 :lighter nil ;; (" NLinum" nlinum--desc)
55 (jit-lock-unregister #'nlinum--region)
56 (remove-hook 'window-configuration-change-hook #'nlinum--setup-window t)
57 (remove-hook 'text-scale-mode-hook #'nlinum--setup-window t)
58 (remove-hook 'after-change-functions #'nlinum--after-change t)
59 (kill-local-variable 'nlinum--line-number-cache)
60 (remove-overlays (point-min) (point-max) 'nlinum t)
61 ;; (kill-local-variable 'nlinum--ol-counter)
62 (kill-local-variable 'nlinum--width)
64 ;; FIXME: Another approach would be to make the mode permanent-local,
65 ;; which might indeed be preferable.
66 (add-hook 'change-major-mode-hook (lambda () (nlinum-mode -1)))
67 (add-hook 'text-scale-mode-hook #'nlinum--setup-window nil t)
68 (add-hook 'window-configuration-change-hook #'nlinum--setup-window nil t)
69 (add-hook 'after-change-functions #'nlinum--after-change nil t)
70 (jit-lock-register #'nlinum--region t))
71 (nlinum--setup-windows))
73 (defun nlinum--face-height (face)
74 (aref (font-info (face-font face)) 2))
76 (defun nlinum--face-width (face) ;New info only in Emacs>=25.
77 (let ((fi (font-info (face-font face))))
78 (when (> (length fi) 11)
79 (let ((width (aref fi 11)))
84 (defun nlinum--setup-window ()
85 ;; FIXME: The interaction between different uses of the margin is
86 ;; problematic. We should have a way for different packages to indicate (and
87 ;; change) their preference independently.
88 (let* ((width (if (display-graphic-p)
90 (let ((width (nlinum--face-width 'linum)))
92 (/ (* nlinum--width 1.0 width)
94 (/ (* nlinum--width 1.0
95 (nlinum--face-height 'linum))
96 (frame-char-height)))))
98 (cur-margins (window-margins))
99 (cur-margin (car cur-margins))
100 ;; (EXT . OURS) keeps track of the size of the margin, where EXT is the
101 ;; size chosen by external code and OURS is the size we last set.
102 ;; OURS is used to detect when someone else modifies the margin.
103 (margin-settings (window-parameter nil 'linum--margin)))
105 (unless (eq (cdr margin-settings) cur-margin)
106 ;; Damn! The margin is not what it used to be! => Update EXT!
107 (setcar margin-settings cur-margin))
108 (set-window-parameter nil 'linum--margin
109 (setq margin-settings (list cur-margin))))
110 (and (car margin-settings) width
111 (setq width (max width (car margin-settings))))
112 (setcdr margin-settings width)
113 (set-window-margins nil (if nlinum-mode width (car margin-settings))
116 (defun nlinum--setup-windows ()
117 (dolist (win (get-buffer-window-list nil nil t))
118 (with-selected-window win (nlinum--setup-window))))
120 (defun nlinum--flush ()
121 (nlinum--setup-windows)
122 ;; (kill-local-variable 'nlinum--ol-counter)
123 (remove-overlays (point-min) (point-max) 'nlinum t)
124 (run-with-timer 0 nil
126 (with-current-buffer buf
127 (with-silent-modifications
128 ;; FIXME: only remove `fontified' on those parts of the
129 ;; buffer that had an nlinum overlay!
130 (remove-text-properties
131 (point-min) (point-max) '(fontified)))))
134 ;; (defun nlinum--ol-count ()
136 ;; (dolist (ol (overlays-in (point-min) (point-max)))
137 ;; (when (overlay-get ol 'nlinum) (incf i)))
140 ;; (defvar nlinum--ol-counter 100)
141 ;; (make-variable-buffer-local 'nlinum--ol-counter)
143 ;; (defun nlinum--flush-overlays (buffer)
144 ;; (with-current-buffer buffer
145 ;; (kill-local-variable 'nlinum--ol-counter)
146 ;; ;; We've created many overlays in this buffer, which can slow
147 ;; ;; down operations significantly. Let's flush them.
148 ;; ;; An easy way to flush them is
149 ;; ;; (remove-overlays min max 'nlinum t)
150 ;; ;; (put-text-property min max 'fontified nil)
151 ;; ;; but if the visible part of the buffer requires more than
152 ;; ;; nlinum-overlay-threshold overlays, then we'll inf-loop.
153 ;; ;; So let's be more careful about removing overlays.
154 ;; (let ((windows (get-buffer-window-list nil nil t))
155 ;; (start (point-min))
156 ;; (debug-count (nlinum--ol-count)))
157 ;; (with-silent-modifications
158 ;; (while (< start (point-max))
159 ;; (let ((end (point-max)))
160 ;; (dolist (window windows)
162 ;; ((< start (1- (window-start window)))
163 ;; (setq end (min (1- (window-start window)) end)))
164 ;; ((< start (1+ (window-end window)))
165 ;; (setq start (1+ (window-end window))))))
166 ;; (when (< start end)
167 ;; (remove-overlays start end 'nlinum t)
168 ;; ;; Warn jit-lock that this part of the buffer is not done any
169 ;; ;; more. This has the downside that font-lock will be re-applied
170 ;; ;; as well. But jit-lock doesn't know how to (and doesn't want
171 ;; ;; to) keep track of the status of its various
172 ;; ;; clients independently.
173 ;; (put-text-property start end 'fontified nil)
174 ;; (setq start (+ end 1))))))
175 ;; (let ((debug-new-count (nlinum--ol-count)))
176 ;; (message "Flushed %d overlays, %d remaining"
177 ;; (- debug-count debug-new-count) debug-new-count)))))
180 (defvar nlinum--line-number-cache nil)
181 (make-variable-buffer-local 'nlinum--line-number-cache)
183 ;; We could try and avoid flushing the cache at every change, e.g. with:
184 ;; (defun nlinum--before-change (start _end)
185 ;; (if (and nlinum--line-number-cache
186 ;; (< start (car nlinum--line-number-cache)))
187 ;; (save-excursion (goto-char start) (nlinum--line-number-at-pos))))
188 ;; But it's far from clear that it's worth the trouble. The current simplistic
189 ;; approach seems to be good enough in practice.
191 (defun nlinum--after-change (&rest _args)
192 (setq nlinum--line-number-cache nil))
194 (defun nlinum--line-number-at-pos ()
195 "Like `line-number-at-pos' but sped up with a cache."
198 (if (and nlinum--line-number-cache
199 (> (- (point) (point-min))
200 (abs (- (point) (car nlinum--line-number-cache)))))
201 (funcall (if (> (point) (car nlinum--line-number-cache))
203 (cdr nlinum--line-number-cache)
204 (count-lines (point) (car nlinum--line-number-cache)))
205 (line-number-at-pos))))
206 ;;(assert (= pos (line-number-at-pos)))
207 (setq nlinum--line-number-cache (cons (point) pos))
210 (defcustom nlinum-format "%d"
211 "Format of the line numbers.
212 Used by the default `nlinum-format-function'."
216 (defvar nlinum-format-function
218 (let ((str (format nlinum-format line)))
219 (when (< (length str) width)
220 ;; Left pad to try and right-align the line-numbers.
221 (setq str (concat (make-string (- width (length str)) ?\ ) str)))
222 (put-text-property 0 width 'face 'linum str)
224 "Function to build the string representing the line number.
225 Takes 2 arguments LINE and WIDTH, both of them numbers, and should return
226 a string. WIDTH is the ideal width of the result. If the result is larger,
227 it may cause the margin to be resized and line numbers to be recomputed.")
229 (defun nlinum--region (start limit)
231 ;; Text may contain those nasty intangible properties, but
232 ;; that shouldn't prevent us from counting those lines.
233 (let ((inhibit-point-motion-hooks t))
235 (unless (bolp) (forward-line 1))
236 (remove-overlays (point) limit 'nlinum t)
237 (let ((line (nlinum--line-number-at-pos)))
239 (and (not (eobp)) (< (point) limit)
240 (let* ((ol (make-overlay (point) (1+ (point))))
241 (str (funcall nlinum-format-function
243 (width (string-width str)))
244 (when (< nlinum--width width)
245 (setq nlinum--width width)
247 (overlay-put ol 'nlinum t)
248 (overlay-put ol 'evaporate t)
249 (overlay-put ol 'before-string
250 (propertize " " 'display
251 `((margin left-margin) ,str)))
252 ;; (setq nlinum--ol-counter (1- nlinum--ol-counter))
253 ;; (when (= nlinum--ol-counter 0)
254 ;; (run-with-idle-timer 0.5 nil #'nlinum--flush-overlays
255 ;; (current-buffer)))
256 (setq line (1+ line))
257 (zerop (forward-line 1))))))))
258 ;; (setq nlinum--desc (format "-%d" (nlinum--ol-count)))
262 (define-globalized-minor-mode global-nlinum-mode nlinum-mode
263 (lambda () (unless (minibufferp) (nlinum-mode))))
266 ;;; nlinum.el ends here