]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | ;;; highlight-indentation.el --- Minor modes for highlighting indentation |
2 | ;; Author: Anton Johansson <anton.johansson@gmail.com> - http://antonj.se | |
3 | ;; Created: Dec 15 23:42:04 2010 | |
4 | ;; Version: 0.7.0 | |
5 | ;; URL: https://github.com/antonj/Highlight-Indentation-for-Emacs | |
6 | ;; | |
7 | ;; This program is free software; you can redistribute it and/or | |
8 | ;; modify it under the terms of the GNU General Public License as | |
9 | ;; published by the Free Software Foundation; either version 2 of | |
10 | ;; the License, or (at your option) any later version. | |
11 | ;; | |
12 | ;; This program is distributed in the hope that it will be | |
13 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied | |
14 | ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
15 | ;; PURPOSE. See the GNU General Public License for more details. | |
16 | ;; | |
17 | ;;; Commentary: | |
18 | ;; Customize `highlight-indentation-face', and | |
19 | ;; `highlight-indentation-current-column-face' to suit your theme. | |
20 | ||
21 | ;;; Code: | |
22 | ||
23 | (defgroup highlight-indentation nil | |
24 | "Highlight Indentation" | |
25 | :prefix "highlight-indentation-" | |
26 | :group 'basic-faces) | |
27 | ||
28 | (defface highlight-indentation-face | |
29 | ;; Fringe has non intrusive color in most color-themes | |
30 | '((t :inherit fringe)) | |
31 | "Basic face for highlighting indentation guides." | |
32 | :group 'highlight-indentation) | |
33 | ||
34 | (defcustom highlight-indentation-offset | |
35 | (if (and (boundp 'standard-indent) standard-indent) standard-indent 2) | |
36 | "Default indentation offset, used if no other can be found from | |
37 | major mode. This value is always used by | |
38 | `highlight-indentation-mode' if set buffer local. Set buffer | |
39 | local with `highlight-indentation-set-offset'" | |
40 | :type 'integer | |
41 | :group 'highlight-indentation) | |
42 | ||
43 | (defcustom highlight-indentation-blank-lines nil | |
44 | "Show indentation guides on blank lines. Experimental. | |
45 | ||
46 | Known issues: | |
47 | - Doesn't work well with completion popups that use overlays | |
48 | - Overlays on blank lines sometimes aren't cleaned up or updated perfectly | |
49 | Can be refreshed by scrolling | |
50 | - Not yet implemented for highlight-indentation-current-column-mode | |
51 | - May not work perfectly near the bottom of the screen | |
52 | - Point appears after indent guides on blank lines" | |
53 | :type 'boolean | |
54 | :group 'highlight-indentation) | |
55 | ||
56 | (defvar highlight-indentation-overlay-priority 1) | |
57 | (defvar highlight-indentation-current-column-overlay-priority 2) | |
58 | ||
59 | (defconst highlight-indentation-hooks | |
60 | '((after-change-functions (lambda (start end length) | |
61 | (highlight-indentation-redraw-region | |
62 | start end | |
63 | 'highlight-indentation-overlay | |
64 | 'highlight-indentation-put-overlays-region)) | |
65 | t t) | |
66 | (window-scroll-functions (lambda (win start) | |
67 | (highlight-indentation-redraw-window | |
68 | win | |
69 | 'highlight-indentation-overlay | |
70 | 'highlight-indentation-put-overlays-region | |
71 | start)) | |
72 | nil t))) | |
73 | ||
74 | (defun highlight-indentation-get-buffer-windows (&optional all-frames) | |
75 | "Return a list of windows displaying the current buffer." | |
76 | (get-buffer-window-list (current-buffer) 'no-minibuf all-frames)) | |
77 | ||
78 | (defun highlight-indentation-delete-overlays-buffer (overlay) | |
79 | "Delete all overlays in the current buffer." | |
80 | (save-restriction | |
81 | (widen) | |
82 | (highlight-indentation-delete-overlays-region (point-min) (point-max) overlay))) | |
83 | ||
84 | (defun highlight-indentation-delete-overlays-region (start end overlay) | |
85 | "Delete overlays between START and END." | |
86 | (mapc #'(lambda (o) | |
87 | (if (overlay-get o overlay) (delete-overlay o))) | |
88 | (overlays-in start end))) | |
89 | ||
90 | (defun highlight-indentation-redraw-window (win overlay func &optional start) | |
91 | "Redraw win starting from START." | |
92 | (highlight-indentation-redraw-region (or start (window-start win)) (window-end win t) overlay func)) | |
93 | ||
94 | (defun highlight-indentation-redraw-region (start end overlay func) | |
95 | "Erase and read overlays between START and END." | |
96 | (save-match-data | |
97 | (save-excursion | |
98 | (let ((inhibit-point-motion-hooks t) | |
99 | (start (save-excursion (goto-char start) (beginning-of-line) (point))) | |
100 | ||
101 | (end (save-excursion (goto-char end) (line-beginning-position 2)))) | |
102 | (highlight-indentation-delete-overlays-region start end overlay) | |
103 | (funcall func start end overlay))))) | |
104 | ||
105 | (defun highlight-indentation-redraw-all-windows (overlay func &optional all-frames) | |
106 | "Redraw the all windows showing the current buffer." | |
107 | (dolist (win (highlight-indentation-get-buffer-windows all-frames)) | |
108 | (highlight-indentation-redraw-window win overlay func))) | |
109 | ||
110 | (defun highlight-indentation-put-overlays-region (start end overlay) | |
111 | "Place overlays between START and END." | |
112 | (goto-char end) | |
113 | (let (o ;; overlay | |
114 | (last-indent 0) | |
115 | (last-char 0) | |
116 | (pos (point)) | |
117 | (loop t)) | |
118 | (while (and loop | |
119 | (>= pos start)) | |
120 | (save-excursion | |
121 | (beginning-of-line) | |
122 | (let ((c 0) | |
123 | (cur-column (current-column))) | |
124 | (while (and (setq c (char-after)) | |
125 | (integerp c) | |
126 | (not (= 10 c)) ;; newline | |
127 | (= 32 c)) ;; space | |
128 | (when (= 0 (% cur-column highlight-indentation-offset)) | |
129 | (let ((p (point))) | |
130 | (setq o (make-overlay p (+ p 1)))) | |
131 | (overlay-put o overlay t) | |
132 | (overlay-put o 'priority highlight-indentation-overlay-priority) | |
133 | (overlay-put o 'face 'highlight-indentation-face)) | |
134 | (forward-char) | |
135 | (setq cur-column (current-column))) | |
136 | (when (and highlight-indentation-blank-lines | |
137 | (integerp c) | |
138 | (or (= 10 c) | |
139 | (= 13 c))) | |
140 | (when (< cur-column last-indent) | |
141 | (let ((column cur-column) | |
142 | (s nil) | |
143 | (show t) | |
144 | num-spaces) | |
145 | (while (< column last-indent) | |
146 | (if (>= 0 | |
147 | (setq num-spaces | |
148 | (% | |
149 | (- last-indent column) | |
150 | highlight-indentation-offset))) | |
151 | (progn | |
152 | (setq num-spaces (1- highlight-indentation-offset)) | |
153 | (setq show t)) | |
154 | (setq show nil)) | |
155 | (setq s (cons (concat | |
156 | (if show | |
157 | (propertize " " | |
158 | 'face | |
159 | 'highlight-indentation-face) | |
160 | "") | |
161 | (make-string num-spaces 32)) | |
162 | s)) | |
163 | (setq column (+ column num-spaces (if show 1 0)))) | |
164 | (setq s (apply 'concat (reverse s))) | |
165 | (let ((p (point))) | |
166 | (setq o (make-overlay p p))) | |
167 | (overlay-put o overlay t) | |
168 | (overlay-put o 'priority highlight-indentation-overlay-priority) | |
169 | (overlay-put o 'after-string s)) | |
170 | (setq cur-column last-indent))) | |
171 | (setq last-indent (* highlight-indentation-offset | |
172 | (ceiling (/ (float cur-column) | |
173 | highlight-indentation-offset)))))) | |
174 | (when (= pos start) | |
175 | (setq loop nil)) | |
176 | (forward-line -1) ;; previous line | |
177 | (setq pos (point))))) | |
178 | ||
179 | (defun highlight-indentation-guess-offset () | |
180 | "Get indentation offset of current buffer." | |
181 | (cond ((and (eq major-mode 'python-mode) (boundp 'python-indent)) | |
182 | python-indent) | |
183 | ((and (eq major-mode 'python-mode) (boundp 'py-indent-offset)) | |
184 | py-indent-offset) | |
185 | ((and (eq major-mode 'python-mode) (boundp 'python-indent-offset)) | |
186 | python-indent-offset) | |
187 | ((and (eq major-mode 'ruby-mode) (boundp 'ruby-indent-level)) | |
188 | ruby-indent-level) | |
189 | ((and (eq major-mode 'scala-mode) (boundp 'scala-indent:step)) | |
190 | scala-indent:step) | |
191 | ((and (eq major-mode 'scala-mode) (boundp 'scala-mode-indent:step)) | |
192 | scala-mode-indent:step) | |
193 | ((and (or (eq major-mode 'scss-mode) (eq major-mode 'css-mode)) (boundp 'css-indent-offset)) | |
194 | css-indent-offset) | |
195 | ((and (eq major-mode 'nxml-mode) (boundp 'nxml-child-indent)) | |
196 | nxml-child-indent) | |
197 | ((and (eq major-mode 'coffee-mode) (boundp 'coffee-tab-width)) | |
198 | coffee-tab-width) | |
199 | ((and (eq major-mode 'js-mode) (boundp 'js-indent-level)) | |
200 | js-indent-level) | |
201 | ((and (eq major-mode 'js2-mode) (boundp 'js2-basic-offset)) | |
202 | js2-basic-offset) | |
203 | ((and (fboundp 'derived-mode-class) (eq (derived-mode-class major-mode) 'sws-mode) (boundp 'sws-tab-width)) | |
204 | sws-tab-width) | |
205 | ((and (eq major-mode 'web-mode) (boundp 'web-mode-markup-indent-offset)) | |
206 | web-mode-markup-indent-offset) ; other similar vars: web-mode-{css-indent,scripts}-offset | |
207 | ((and (eq major-mode 'web-mode) (boundp 'web-mode-html-offset)) ; old var | |
208 | web-mode-html-offset) | |
209 | ((and (local-variable-p 'c-basic-offset) (boundp 'c-basic-offset)) | |
210 | c-basic-offset) | |
211 | ((and (eq major-mode 'yaml-mode) (boundp 'yaml-indent-offset)) | |
212 | yaml-indent-offset) | |
213 | ((and (eq major-mode 'elixir-mode) (boundp 'elixir-smie-indent-basic)) | |
214 | elixir-smie-indent-basic) | |
215 | (t | |
216 | (default-value 'highlight-indentation-offset)))) | |
217 | ||
218 | ;;;###autoload | |
219 | (define-minor-mode highlight-indentation-mode | |
220 | "Highlight indentation minor mode highlights indentation based on spaces" | |
221 | :lighter " ||" | |
222 | (when (not highlight-indentation-mode) ;; OFF | |
223 | (highlight-indentation-delete-overlays-buffer 'highlight-indentation-overlay) | |
224 | (dolist (hook highlight-indentation-hooks) | |
225 | (remove-hook (car hook) (nth 1 hook) (nth 3 hook)))) | |
226 | ||
227 | (when highlight-indentation-mode ;; ON | |
228 | (when (not (local-variable-p 'highlight-indentation-offset)) | |
229 | (set (make-local-variable 'highlight-indentation-offset) | |
230 | (highlight-indentation-guess-offset))) | |
231 | ||
232 | ;; Setup hooks | |
233 | (dolist (hook highlight-indentation-hooks) | |
234 | (apply 'add-hook hook)) | |
235 | (highlight-indentation-redraw-all-windows 'highlight-indentation-overlay | |
236 | 'highlight-indentation-put-overlays-region))) | |
237 | ||
238 | ;;;###autoload | |
239 | (defun highlight-indentation-set-offset (offset) | |
240 | "Set indentation offset locally in buffer, will prevent | |
241 | highlight-indentation from trying to guess indentation offset | |
242 | from major mode" | |
243 | (interactive | |
244 | (if (and current-prefix-arg (not (consp current-prefix-arg))) | |
245 | (list (prefix-numeric-value current-prefix-arg)) | |
246 | (list (read-number "Indentation offset: ")))) | |
247 | (set (make-local-variable 'highlight-indentation-offset) offset) | |
248 | (when highlight-indentation-mode | |
249 | (highlight-indentation-mode))) | |
250 | ||
251 | ;;; This minor mode will highlight the indentation of the current line | |
252 | ;;; as a vertical bar (grey background color) aligned with the column of the | |
253 | ;;; first character of the current line. | |
254 | (defface highlight-indentation-current-column-face | |
255 | ;; Fringe has non intrusive color in most color-themes | |
256 | '((t (:background "black"))) | |
257 | "Basic face for highlighting indentation guides." | |
258 | :group 'highlight-indentation) | |
259 | ||
260 | (defconst highlight-indentation-current-column-hooks | |
261 | '((post-command-hook (lambda () | |
262 | (highlight-indentation-redraw-all-windows 'highlight-indentation-current-column-overlay | |
263 | 'highlight-indentation-current-column-put-overlays-region)) nil t))) | |
264 | ||
265 | (defun highlight-indentation-current-column-put-overlays-region (start end overlay) | |
266 | "Place overlays between START and END." | |
267 | (let (o ;; overlay | |
268 | (last-indent 0) | |
269 | (indent (save-excursion (back-to-indentation) (current-column))) | |
270 | (pos start)) | |
271 | (goto-char start) | |
272 | ;; (message "doing it %d" indent) | |
273 | (while (< pos end) | |
274 | (beginning-of-line) | |
275 | (while (and (integerp (char-after)) | |
276 | (not (= 10 (char-after))) ;; newline | |
277 | (= 32 (char-after))) ;; space | |
278 | (when (= (current-column) indent) | |
279 | (setq pos (point) | |
280 | last-indent pos | |
281 | o (make-overlay pos (+ pos 1))) | |
282 | (overlay-put o overlay t) | |
283 | (overlay-put o 'priority highlight-indentation-current-column-overlay-priority) | |
284 | (overlay-put o 'face 'highlight-indentation-current-column-face)) | |
285 | (forward-char)) | |
286 | (forward-line) ;; Next line | |
287 | (setq pos (point))))) | |
288 | ||
289 | ;;;###autoload | |
290 | (define-minor-mode highlight-indentation-current-column-mode | |
291 | "Highlight Indentation minor mode displays a vertical bar | |
292 | corresponding to the indentation of the current line" | |
293 | :lighter " |" | |
294 | ||
295 | (when (not highlight-indentation-current-column-mode) ;; OFF | |
296 | (highlight-indentation-delete-overlays-buffer 'highlight-indentation-current-column-overlay) | |
297 | (dolist (hook highlight-indentation-current-column-hooks) | |
298 | (remove-hook (car hook) (nth 1 hook) (nth 3 hook)))) | |
299 | ||
300 | (when highlight-indentation-current-column-mode ;; ON | |
301 | (when (not (local-variable-p 'highlight-indentation-offset)) | |
302 | (set (make-local-variable 'highlight-indentation-offset) | |
303 | (highlight-indentation-guess-offset))) | |
304 | ||
305 | ;; Setup hooks | |
306 | (dolist (hook highlight-indentation-current-column-hooks) | |
307 | (apply 'add-hook hook)) | |
308 | (highlight-indentation-redraw-all-windows 'highlight-indentation-current-column-overlay | |
309 | 'highlight-indentation-current-column-put-overlays-region))) | |
310 | ||
311 | (provide 'highlight-indentation) | |
312 | ||
313 | ;;; highlight-indentation.el ends here |